From a62c3345d14ffee95aef577d4967346adad752fd Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 28 Sep 2020 16:37:10 +0200 Subject: [PATCH 01/97] Add docs on CodeQL Design Patterns --- docs/ql-design-patterns.md | 78 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/ql-design-patterns.md diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md new file mode 100644 index 00000000000..450dd99b9b9 --- /dev/null +++ b/docs/ql-design-patterns.md @@ -0,0 +1,78 @@ +# CodeQL Design Patterns + +A list of design patterns you are recommended to follow. + +## `::Range` for extensibility and refinement + +To allow both extensibility and refinement of classes, we use what is commonly referred to as the `::Range` pattern (since https://github.com/github/codeql/pull/727), but the actual implementation can use different names. + +
+Generic example of how to define classes with ::Range + +Instead of +```ql +/** */ +abstract class MySpecialExpr extends Expr { + /** */ + abstract int memberPredicate(); +} +``` +with +```ql +class ConcreteSubclass extends MySpecialExpr { ... } +``` + +use + +```ql +/** + * + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `MySpecialExpr::Range` instead. + */ +class MySpecialExpr extends Expr { + MySpecialExpr::Range self; + + MySpecialExpr() { this = self } + + /** */ + int memberPredicate() { result = self.memberPredicate() } +} + +/** Provides a class for modeling new <...> APIs. */ +module MySpecialExpr { + /** + * + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `MySpecialExpr` instead. + */ + abstract class Range extends Expr { + /** */ + abstract int memberPredicate(); + } +} +``` +with +```ql +class ConcreteSubclass extends MySpecialExpr::Range { ... } +``` + +
+ +### Rationale + +Let's use an example from the Go libraries: https://github.com/github/codeql-go/blob/2ba9bbfd8ba1818b5ee9f6009c86a605189c9ef3/ql/src/semmle/go/Concepts.qll#L119-L157 + +`EscapeFunction`, as the name suggests, models various APIs that escape meta-characters. It has a member-predicate `kind()` that tells you what sort of escaping the modelled function does. For example, if the result of that predicate is `"js"`, then this means that the escaping function is meant to make things safe to embed inside JavaScript. +`EscapeFunction::Range` is subclassed to model various APIs, and `kind()` is implemented accordingly. +But we can also subclass `EscapeFunction` to, as in the above example, talk about all JS-escaping functions. + +You can, of course, do the same without the `::Range` pattern, but it's a little cumbersome: +If you only had an `abstract class EscapeFunction { ... }`, then `JsEscapeFunction` would need to be implemented in a slightly tricky way to prevent it from extending `EscapeFunction` (instead of refining it). You would have to give it a charpred `this instanceof EscapeFunction`, which looks useless but isn't. And additionally, you'd have to provide trivial `none()` overrides of all the abstract predicates defined in `EscapeFunction`. This is all pretty awkward, and we can avoid it by distinguishing between `EscapeFunction` and `EscapeFunction::Range`. + + +## Importing all subclasses of abstract base class + +When providing an abstract class, you should ensure that all subclasses are included when the abstract class is (unless you have good reason not to). Otherwise you risk having different meanings of the abstract class depending on what you happen to import. From e859a804c49e750e810d61c4ea507916a2b04e1a Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Sep 2020 09:05:18 +0200 Subject: [PATCH 02/97] Update docs on CodeQL design patterns --- docs/ql-design-patterns.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index 450dd99b9b9..91f5f003232 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -76,3 +76,5 @@ If you only had an `abstract class EscapeFunction { ... }`, then `JsEscapeFuncti ## Importing all subclasses of abstract base class When providing an abstract class, you should ensure that all subclasses are included when the abstract class is (unless you have good reason not to). Otherwise you risk having different meanings of the abstract class depending on what you happen to import. + +One example where this _does not_ apply: `DataFlow::Configuration` and its variants are abstract, but we generally do not want to import all configurations into the same scope at once. From f5010038791a0edcd9dd4a7128f38301faf81ae4 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 30 Sep 2020 14:28:08 +0200 Subject: [PATCH 03/97] Design Patterns: Recommend `this = range` for ::Range pattern --- docs/ql-design-patterns.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index 91f5f003232..c105f848e48 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -32,12 +32,12 @@ use * extend `MySpecialExpr::Range` instead. */ class MySpecialExpr extends Expr { - MySpecialExpr::Range self; + MySpecialExpr::Range range; - MySpecialExpr() { this = self } + MySpecialExpr() { this = range } /** */ - int memberPredicate() { result = self.memberPredicate() } + int memberPredicate() { result = range.memberPredicate() } } /** Provides a class for modeling new <...> APIs. */ @@ -61,6 +61,8 @@ class ConcreteSubclass extends MySpecialExpr::Range { ... } +Note, previously we used to write `MySpecialExpr() { this = self }`, but we now recommend using `MySpecialExpr() { this = range }` instead to avoid anyone mistakenly thinking that `self` and `this` are synonyms in general. + ### Rationale Let's use an example from the Go libraries: https://github.com/github/codeql-go/blob/2ba9bbfd8ba1818b5ee9f6009c86a605189c9ef3/ql/src/semmle/go/Concepts.qll#L119-L157 From c04e96453d9f9d96826da627bfa9d20e5b5f3668 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 3 Nov 2020 11:07:11 +0100 Subject: [PATCH 04/97] Update ::Range part of CodeQL design patterns Co-authored-by: Pavel Avgustinov <54942558+p0@users.noreply.github.com> --- docs/ql-design-patterns.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index c105f848e48..afa273317cb 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -6,23 +6,23 @@ A list of design patterns you are recommended to follow. To allow both extensibility and refinement of classes, we use what is commonly referred to as the `::Range` pattern (since https://github.com/github/codeql/pull/727), but the actual implementation can use different names. +This pattern should be used when you want to model a user-extensible set of values ("extensibility"), while allowing restrictive subclasses, typically for the purposes of overriding predicates ("refinement"). Using a simple `abstract` class gives you the former, but makes it impossible to create overriding methods for all contributing extensions at once. Using a non-`abstract` class provides refinement-based overriding, but requires the original class to range over a closed, non-extensible set.
Generic example of how to define classes with ::Range -Instead of +Using a single `abstract` class looks like this: ```ql /** */ abstract class MySpecialExpr extends Expr { /** */ abstract int memberPredicate(); } -``` -with -```ql class ConcreteSubclass extends MySpecialExpr { ... } ``` -use +While this allows users of the library to add new types of `MySpecialExpr` (like, in this case, `ConcreteSubclass), there is no way to override the implementations of `memberPredicate` of all extensions at once. + +Applying the `::Range` pattern yields the following: ```ql /** @@ -54,10 +54,9 @@ module MySpecialExpr { } } ``` -with -```ql -class ConcreteSubclass extends MySpecialExpr::Range { ... } -``` +Now, a concrete subclass can derive from `MySpecialExpr::Range` if it wants to extend the set of values in `MySpecialExpr`, and it will be required to implement the abstract `memberPredicate()`. Conversely, if it wants to refine `MySpecialExpr` and override `memberPredicate` for all extensions, it can do so by deriving from `MySpecialExpr` directly. + +The key element of the pattern is to provide a field of type `MySpecialExpr::Range`, equating it to `this` in the characteristic predicate of `MySpecialExpr`. In member predicates, we can use either `this` or `range`, depending on which type has the API we need.
From 76a0db84ee3ec8f963b8882967e570a798b86b71 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 5 Nov 2020 04:25:21 +0000 Subject: [PATCH 05/97] Query for detecting Local Android DoS caused by NFE --- .../Security/CWE/CWE-755/NFEAndroidDoS.java | 18 +++ .../Security/CWE/CWE-755/NFEAndroidDoS.qhelp | 40 +++++++ .../Security/CWE/CWE-755/NFEAndroidDoS.ql | 107 ++++++++++++++++++ .../security/CWE-755/AndroidManifest.xml | 24 ++++ .../security/CWE-755/NFEAndroidDoS.expected | 22 ++++ .../security/CWE-755/NFEAndroidDoS.java | 49 ++++++++ .../security/CWE-755/NFEAndroidDoS.qlref | 1 + .../query-tests/security/CWE-755/options | 1 + 8 files changed, 262 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql create mode 100755 java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.qlref create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/options diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.java b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.java new file mode 100644 index 00000000000..ad2f01dd91e --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.java @@ -0,0 +1,18 @@ +public class NFEAndroidDoS extends Activity { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_view); + + // BAD: Uncaught NumberFormatException due to remote user inputs + { + String minPriceStr = getIntent().getStringExtra("priceMin"); + double minPrice = Double.parseDouble(minPriceStr); + } + + // GOOD: Use the proper Android method to get number extra + { + int width = getIntent().getIntExtra("width", 0); + int height = getIntent().getIntExtra("height", 0); + } + } +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.qhelp b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.qhelp new file mode 100644 index 00000000000..b454e7a5272 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.qhelp @@ -0,0 +1,40 @@ + + + + +

NumberFormatException (NFE) thrown but not caught by an Android application will crash the application. If the application allows external inputs, an attacker can send an invalid number as intent extra to trigger NFE, which introduces local Denial of Service (Dos) attack.

+

+ This is a common problem in Android development since Android components don't have + throw Exception(...) + in their class and method definitions. +

+
+ + +

+ Use the Android methods intended to get number extras e.g. + Intent.getFloatExtra(String name, float defaultValue) + since they have the built-in try/catch processing, or explicitly do try/catch in the application. +

+
+ + +

The following example shows both 'BAD' and 'GOOD' configurations. In the 'BAD' configuration, number value is retrieved as string extra then parsed to double. In the 'GOOD' configuration, number value is retrieved as integer extra.

+ +
+ + +
  • + CWE: + CWE-755: Improper Handling of Exceptional Conditions +
  • +
  • + Android Developers: + Android Crashes +
  • +
  • + Google Analytics: + Crash and Exception Measurement Using the Google Analytics SDK +
  • +
    +
    \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql new file mode 100644 index 00000000000..5002488f91b --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql @@ -0,0 +1,107 @@ +/** + * @name Local Android DoS Caused By NumberFormatException + * @id java/android/nfe-local-android-dos + * @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, which is a local Denial of Service (Dos) attack. + * @kind path-problem + * @tags security + * external/cwe/cwe-755 + */ + +import java +import semmle.code.java.frameworks.android.Intent +import semmle.code.java.frameworks.android.WebView +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +/** Code from java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql */ +private class SpecialMethodAccess extends MethodAccess { + predicate isValueOfMethod(string klass) { + this.getMethod().getName() = "valueOf" and + this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and + this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") + } + + predicate isParseMethod(string klass, string name) { + this.getMethod().getName() = name and + this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) + } + + predicate throwsNFE() { + this.isParseMethod("Byte", "parseByte") or + this.isParseMethod("Short", "parseShort") or + this.isParseMethod("Integer", "parseInt") or + this.isParseMethod("Long", "parseLong") or + this.isParseMethod("Float", "parseFloat") or + this.isParseMethod("Double", "parseDouble") or + this.isParseMethod("Byte", "decode") or + this.isParseMethod("Short", "decode") or + this.isParseMethod("Integer", "decode") or + this.isParseMethod("Long", "decode") or + this.isValueOfMethod("Byte") or + this.isValueOfMethod("Short") or + this.isValueOfMethod("Integer") or + this.isValueOfMethod("Long") or + this.isValueOfMethod("Float") or + this.isValueOfMethod("Double") + } +} + +private class SpecialClassInstanceExpr extends ClassInstanceExpr { + predicate isStringConstructor(string klass) { + this.getType().(RefType).hasQualifiedName("java.lang", klass) and + this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and + this.getNumArgument() = 1 + } + + predicate throwsNFE() { + this.isStringConstructor("Byte") or + this.isStringConstructor("Short") or + this.isStringConstructor("Integer") or + this.isStringConstructor("Long") or + this.isStringConstructor("Float") or + this.isStringConstructor("Double") + } +} + +class NumberFormatException extends RefType { + NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") } +} + +private predicate catchesNFE(TryStmt t) { + exists(CatchClause cc, LocalVariableDeclExpr v | + t.getACatchClause() = cc and + cc.getVariable() = v and + v.getType().(RefType).getASubtype*() instanceof NumberFormatException + ) +} + +private predicate throwsNFE(Expr e) { + e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE() +} + +/** + * Taint configuration tracking flow from untrusted inputs to number conversion calls. + */ +class NFELocalDoSConfiguration extends TaintTracking::Configuration { + NFELocalDoSConfiguration() { this = "NFELocalDoSConfiguration" } + + /** Holds if source is a remote flow source */ + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + /** Holds if NFE is thrown but not caught */ + override predicate isSink(DataFlow::Node sink) { + exists(Expr e | + throwsNFE(e) and + not exists(TryStmt t | + t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and + catchesNFE(t) + ) and + sink.asExpr() = e + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, NFELocalDoSConfiguration conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Local Android Denial of Service due to $@.", source.getNode(), + "user-provided value" diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml b/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml new file mode 100755 index 00000000000..83de20ade2e --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected new file mode 100644 index 00000000000..3c5cd7bec2a --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected @@ -0,0 +1,22 @@ +edges +| NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | +| NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | +| NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | +| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | +| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | +nodes +| NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent | +| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | semmle.label | parseDouble(...) | +| NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent | +| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | semmle.label | parseInt(...) | +| NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent | +| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | semmle.label | parseInt(...) | +| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent | +| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | semmle.label | new Double(...) | +| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | semmle.label | valueOf(...) | +#select +| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java new file mode 100644 index 00000000000..cd2d804c1f5 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java @@ -0,0 +1,49 @@ +package com.example.app; + +import android.app.Activity; +import android.os.Bundle; + +/** Android activity that tests app crash by NumberFormatException */ +public class NFEAndroidDoS extends Activity { + // BAD - parse string extra to double + public void testOnCreate1(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + String minPriceStr = getIntent().getStringExtra("priceMin"); + double minPrice = Double.parseDouble(minPriceStr); + } + + // BAD - parse string extra to integer + public void testOnCreate2(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + String widthStr = getIntent().getStringExtra("width"); + int width = Integer.parseInt(widthStr); + + String heightStr = getIntent().getStringExtra("height"); + int height = Integer.parseInt(heightStr); + } + + // GOOD - parse int extra to integer + public void testOnCreate3(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + int width = getIntent().getIntExtra("width", 0); + int height = getIntent().getIntExtra("height", 0); + } + + // BAD - convert string extra to double + public void testOnCreate4(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + String minPriceStr = getIntent().getStringExtra("priceMin"); + double minPrice = new Double(minPriceStr); + + String maxPriceStr = getIntent().getStringExtra("priceMax"); + double maxPrice = Double.valueOf(minPriceStr); + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.qlref b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.qlref new file mode 100644 index 00000000000..e51bb3c8075 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/options b/java/ql/test/experimental/query-tests/security/CWE-755/options new file mode 100644 index 00000000000..43e25f608b6 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/options @@ -0,0 +1 @@ +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0 From b10552aa2eb535032e02d9dcc7c183cbe56eaf34 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 5 Nov 2020 11:47:32 +0000 Subject: [PATCH 06/97] Specify exported Android components for local Android DoS --- .../ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql index 5002488f91b..e71bcc5e80a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql @@ -9,7 +9,6 @@ import java import semmle.code.java.frameworks.android.Intent -import semmle.code.java.frameworks.android.WebView import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph @@ -80,7 +79,7 @@ private predicate throwsNFE(Expr e) { } /** - * Taint configuration tracking flow from untrusted inputs to number conversion calls. + * Taint configuration tracking flow from untrusted inputs to number conversion calls in exported Android compononents. */ class NFELocalDoSConfiguration extends TaintTracking::Configuration { NFELocalDoSConfiguration() { this = "NFELocalDoSConfiguration" } @@ -91,6 +90,7 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration { /** Holds if NFE is thrown but not caught */ override predicate isSink(DataFlow::Node sink) { exists(Expr e | + e.getEnclosingCallable().getDeclaringType() instanceof ExportableAndroidComponent and throwsNFE(e) and not exists(TryStmt t | t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and From bc899b6337741c36197a249c98309a243ee66630 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Fri, 6 Nov 2020 04:44:56 +0000 Subject: [PATCH 07/97] Move common code to a library and add more test cases --- .../Security/CWE/CWE-755/NFEAndroidDoS.ql | 74 ++----------------- .../security/CWE-755/AndroidManifest.xml | 2 + .../security/CWE-755/IntentUtils.java | 16 ++++ .../security/CWE-755/NFEAndroidDoS.expected | 10 +-- .../security/CWE-755/NFEAndroidDoS.java | 37 ++++++++++ .../security/CWE-755/SafeActivity.java | 16 ++++ 6 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/IntentUtils.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-755/SafeActivity.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql index e71bcc5e80a..81e08b5eba7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql @@ -1,7 +1,7 @@ /** * @name Local Android DoS Caused By NumberFormatException * @id java/android/nfe-local-android-dos - * @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, which is a local Denial of Service (Dos) attack. + * @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, constituting a local Denial of Service (DoS) attack. * @kind path-problem * @tags security * external/cwe/cwe-755 @@ -10,74 +10,9 @@ import java import semmle.code.java.frameworks.android.Intent import semmle.code.java.dataflow.FlowSources +import semmle.code.java.NumberFormatException import DataFlow::PathGraph -/** Code from java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql */ -private class SpecialMethodAccess extends MethodAccess { - predicate isValueOfMethod(string klass) { - this.getMethod().getName() = "valueOf" and - this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and - this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") - } - - predicate isParseMethod(string klass, string name) { - this.getMethod().getName() = name and - this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) - } - - predicate throwsNFE() { - this.isParseMethod("Byte", "parseByte") or - this.isParseMethod("Short", "parseShort") or - this.isParseMethod("Integer", "parseInt") or - this.isParseMethod("Long", "parseLong") or - this.isParseMethod("Float", "parseFloat") or - this.isParseMethod("Double", "parseDouble") or - this.isParseMethod("Byte", "decode") or - this.isParseMethod("Short", "decode") or - this.isParseMethod("Integer", "decode") or - this.isParseMethod("Long", "decode") or - this.isValueOfMethod("Byte") or - this.isValueOfMethod("Short") or - this.isValueOfMethod("Integer") or - this.isValueOfMethod("Long") or - this.isValueOfMethod("Float") or - this.isValueOfMethod("Double") - } -} - -private class SpecialClassInstanceExpr extends ClassInstanceExpr { - predicate isStringConstructor(string klass) { - this.getType().(RefType).hasQualifiedName("java.lang", klass) and - this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and - this.getNumArgument() = 1 - } - - predicate throwsNFE() { - this.isStringConstructor("Byte") or - this.isStringConstructor("Short") or - this.isStringConstructor("Integer") or - this.isStringConstructor("Long") or - this.isStringConstructor("Float") or - this.isStringConstructor("Double") - } -} - -class NumberFormatException extends RefType { - NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") } -} - -private predicate catchesNFE(TryStmt t) { - exists(CatchClause cc, LocalVariableDeclExpr v | - t.getACatchClause() = cc and - cc.getVariable() = v and - v.getType().(RefType).getASubtype*() instanceof NumberFormatException - ) -} - -private predicate throwsNFE(Expr e) { - e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE() -} - /** * Taint configuration tracking flow from untrusted inputs to number conversion calls in exported Android compononents. */ @@ -90,7 +25,7 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration { /** Holds if NFE is thrown but not caught */ override predicate isSink(DataFlow::Node sink) { exists(Expr e | - e.getEnclosingCallable().getDeclaringType() instanceof ExportableAndroidComponent and + e.getEnclosingCallable().getDeclaringType().(ExportableAndroidComponent).isExported() and throwsNFE(e) and not exists(TryStmt t | t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and @@ -103,5 +38,6 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration { from DataFlow::PathNode source, DataFlow::PathNode sink, NFELocalDoSConfiguration conf where conf.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Local Android Denial of Service due to $@.", source.getNode(), +select sink.getNode(), source, sink, + "Uncaught NumberFormatException in an exported Android component due to $@.", source.getNode(), "user-provided value" diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml b/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml index 83de20ade2e..780a710f0ad 100755 --- a/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml +++ b/java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml @@ -19,6 +19,8 @@ + + diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/IntentUtils.java b/java/ql/test/experimental/query-tests/security/CWE-755/IntentUtils.java new file mode 100644 index 00000000000..0a5a8564e1f --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/IntentUtils.java @@ -0,0 +1,16 @@ +package com.example.app; + +import android.app.Activity; + +import android.os.Bundle; + + +/** A utility program for getting intent extra information from Android activity */ +public class IntentUtils { + + /** Get double extra */ + public static double getDoubleExtra(Activity a, String key) { + String value = a.getIntent().getStringExtra(key); + return Double.parseDouble(value); + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected index 3c5cd7bec2a..8f32d7422aa 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.expected @@ -15,8 +15,8 @@ nodes | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | semmle.label | new Double(...) | | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | semmle.label | valueOf(...) | #select -| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value | -| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value | -| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value | -| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | -| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | +| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value | diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java index cd2d804c1f5..bf527f04fe1 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java +++ b/java/ql/test/experimental/query-tests/security/CWE-755/NFEAndroidDoS.java @@ -46,4 +46,41 @@ public class NFEAndroidDoS extends Activity { String maxPriceStr = getIntent().getStringExtra("priceMax"); double maxPrice = Double.valueOf(minPriceStr); } + + // GOOD - parse string extra to double with caught NFE + public void testOnCreate5(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + double minPrice = 0; + try { + String minPriceStr = getIntent().getStringExtra("priceMin"); + minPrice = Double.parseDouble(minPriceStr); + } catch (NumberFormatException nfe) { + nfe.printStackTrace(); + } + } + + // GOOD - parse string extra to double with caught NFE as the supertype Throwable + public void testOnCreate6(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + double minPrice = 0; + try { + String minPriceStr = getIntent().getStringExtra("priceMin"); + minPrice = Double.parseDouble(minPriceStr); + } catch (Throwable te) { + te.printStackTrace(); + } + } + + // BAD - parse string extra to double + // Note this case of invoking utility method that takes an Activity a then calls `a.getIntent().getStringExtra(...)` is not yet detected thus is beyond what the query is capable of. + public void testOnCreate7(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + double priceMin = IntentUtils.getDoubleExtra(this, "priceMin"); + } } \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-755/SafeActivity.java b/java/ql/test/experimental/query-tests/security/CWE-755/SafeActivity.java new file mode 100644 index 00000000000..4bf0d453f08 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-755/SafeActivity.java @@ -0,0 +1,16 @@ +package com.example.app; + +import android.app.Activity; +import android.os.Bundle; + +/** Android activity that tests app crash by NumberFormatException, which is not exported in `AndroidManifest.xml` */ +public class SafeActivity extends Activity { + // BAD - parse string extra to double + public void testOnCreate1(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(-1); + + String minPriceStr = getIntent().getStringExtra("priceMin"); + double minPrice = Double.parseDouble(minPriceStr); + } +} \ No newline at end of file From 14236709f695f47fdfd95376a4707d48f1f5fa40 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 10 Nov 2020 15:39:45 +0100 Subject: [PATCH 08/97] Fix typo ql-design-patterns.md Co-authored-by: Pavel Avgustinov <54942558+p0@users.noreply.github.com> --- docs/ql-design-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index afa273317cb..3a0c43acd43 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -20,7 +20,7 @@ abstract class MySpecialExpr extends Expr { class ConcreteSubclass extends MySpecialExpr { ... } ``` -While this allows users of the library to add new types of `MySpecialExpr` (like, in this case, `ConcreteSubclass), there is no way to override the implementations of `memberPredicate` of all extensions at once. +While this allows users of the library to add new types of `MySpecialExpr` (like, in this case, `ConcreteSubclass`), there is no way to override the implementations of `memberPredicate` of all extensions at once. Applying the `::Range` pattern yields the following: From 60ea9cec6e2f238e39cfd8b7a04145fe86d4bcc3 Mon Sep 17 00:00:00 2001 From: Pavel Avgustinov <54942558+p0@users.noreply.github.com> Date: Tue, 10 Nov 2020 15:59:45 +0000 Subject: [PATCH 09/97] Update docs/ql-design-patterns.md Co-authored-by: Rasmus Wriedt Larsen --- docs/ql-design-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index 3a0c43acd43..ad3137932d8 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -60,7 +60,7 @@ The key element of the pattern is to provide a field of type `MySpecialExpr::Ran -Note, previously we used to write `MySpecialExpr() { this = self }`, but we now recommend using `MySpecialExpr() { this = range }` instead to avoid anyone mistakenly thinking that `self` and `this` are synonyms in general. +Note that in some libraries, the `range` field is in fact called `self`. While we do recommend using `range` for consistency, the name of the field does not matter (and using `range` avoids confusion in contexts like Python analysis that has strong usage of `self`). ### Rationale From 018d5c46da861ce37b6e631d6696eceb197c91d1 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Tue, 10 Nov 2020 21:07:44 +0000 Subject: [PATCH 10/97] Simplify the query --- java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql index 81e08b5eba7..879399be26c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql @@ -28,7 +28,7 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration { e.getEnclosingCallable().getDeclaringType().(ExportableAndroidComponent).isExported() and throwsNFE(e) and not exists(TryStmt t | - t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and + t.getBlock() = e.getAnEnclosingStmt() and catchesNFE(t) ) and sink.asExpr() = e From aab5263c6a80790c4d9eb0bacc263710b6248fe4 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 19 Oct 2020 11:19:48 +0200 Subject: [PATCH 11/97] Dataflow: Add modules. --- .../java/dataflow/internal/DataFlowImpl.qll | 2593 +++++++++-------- 1 file changed, 1306 insertions(+), 1287 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 94f008a7225..d01a7e6be25 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -271,294 +271,300 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false - or +private module Stage1 { + /** + * Holds if `node` is reachable from a source in the configuration `config`. + * + * The Boolean `fromArg` records whether the node is reached through an + * argument in a call. + */ + predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + fromArg = false + or + exists(Node mid | + nodeCandFwd1(mid, fromArg, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + nodeCandFwd1(mid, fromArg, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + nodeCandFwd1(mid, config) and + jumpStep(mid, node, config) and + fromArg = false + ) + or + exists(Node mid | + nodeCandFwd1(mid, config) and + additionalJumpStep(mid, node, config) and + fromArg = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + nodeCandFwd1(mid, fromArg, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + nodeCandFwd1Read(c, node, fromArg, config) and + nodeCandFwd1IsStored(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + nodeCandFwd1(arg, config) and + viableParamArg(_, node, arg) and + fromArg = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + nodeCandFwd1IsEntered(call, fromArg, config) + ) + ) + } + + private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + + pragma[nomagic] + private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { exists(Node mid | nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + read(mid, c, node) ) - or - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) - ) - or - exists(Node mid | + } + + /** + * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. + */ + pragma[nomagic] + private predicate nodeCandFwd1IsStored(Content c, Configuration config) { + exists(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config + ) { + exists(ReturnNodeExt ret | + nodeCandFwd1(ret, fromArg, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate nodeCandFwd1Out( + DataFlowCall call, Node out, boolean fromArg, Configuration config + ) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ + pragma[nomagic] + private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) + } + + pragma[nomagic] + private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { + nodeCandFwd1(node, config) and + config.isSink(node) and + toReturn = false + or + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + nodeCand1Store(c, node, toReturn, config) and + nodeCand1IsRead(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + nodeCandFwd1IsStored(c, unbind(config)) and + nodeCand1(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + nodeCand1In(call, node, false, config) and + toReturn = false + or + nodeCand1InToReturn(call, node, config) and + nodeCand1IsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + nodeCand1Out(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + /** + * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. + */ + pragma[nomagic] + private predicate nodeCand1IsRead(Content c, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + nodeCandFwd1(node, unbind(config)) and + read(node, c, mid) and + nodeCandFwd1IsStored(c, unbind(config)) and + nodeCand1(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + nodeCand1(mid, toReturn, config) and + nodeCandFwd1IsStored(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `nodeCand1`. + */ + predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { + nodeCand1IsRead(c, conf) and + nodeCand1Store(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + nodeCandFwd1ReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + nodeCand1(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) + } + + pragma[nomagic] + private predicate nodeCand1In( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1In(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ + pragma[nomagic] + private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate nodeCand1(Node node, Configuration config) { + nodeCand1(node, _, config) + } } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and + Stage1::nodeCand1(node, true, config) and + Stage1::nodeCandFwd1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -594,8 +600,8 @@ private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration c pragma[nomagic] private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and + Stage1::nodeCand1IsReadAndStored(c, config) and + Stage1::nodeCand1(n2, unbind(config)) and store(n1, tc, n2, _) and c = tc.getContent() ) @@ -603,20 +609,20 @@ private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) pragma[nomagic] private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and + Stage1::nodeCand1IsReadAndStored(c, config) and + Stage1::nodeCand1(n2, unbind(config)) and read(n1, c, n2) } pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::nodeCand1(node1, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::nodeCand1(node1, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +630,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::nodeCand1(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +644,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::nodeCand1(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +653,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::nodeCand1(arg, config) } /** @@ -660,7 +666,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::nodeCand1(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +736,344 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) - ) +private module Stage2 { + /** + * Holds if `node` is reachable from a source in the configuration `config`. + * The Boolean `stored` records whether the tracked value is stored into a + * field of `node`. + * + * The Boolean `fromArg` records whether the node is reached through an + * argument in a call, and if so, `argStored` records whether the tracked + * value was stored into a field of the argument. + */ + private predicate nodeCandFwd2( + Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config + ) { + Stage1::nodeCand1(node, config) and + config.isSource(node) and + fromArg = false and + argStored = TBooleanNone() and + stored = false or - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and - jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - ) - or - // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true - ) - or - // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) - ) - or - // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() - or - // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + Stage1::nodeCand1(node, unbind(config)) and + ( + exists(Node mid | + nodeCandFwd2(mid, fromArg, argStored, stored, config) and + localFlowStepNodeCand1(mid, node, config) + ) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Node mid | + nodeCandFwd2(mid, fromArg, argStored, stored, config) and + additionalLocalFlowStepNodeCand1(mid, node, config) and + stored = false + ) + or + exists(Node mid | + nodeCandFwd2(mid, _, _, stored, config) and + jumpStep(mid, node, config) and + fromArg = false and + argStored = TBooleanNone() + ) + or + exists(Node mid | + nodeCandFwd2(mid, _, _, stored, config) and + additionalJumpStep(mid, node, config) and + fromArg = false and + argStored = TBooleanNone() and + stored = false + ) + or + // store + exists(Node mid | + nodeCandFwd2(mid, fromArg, argStored, _, config) and + storeCand1(mid, _, node, config) and + stored = true + ) + or + // read + exists(Content c | + nodeCandFwd2Read(c, node, fromArg, argStored, config) and + nodeCandFwd2IsStored(c, stored, config) + ) + or + // flow into a callable + nodeCandFwd2In(_, node, _, _, stored, config) and + fromArg = true and + if parameterThroughFlowNodeCand1(node, config) + then argStored = TBooleanSome(stored) + else argStored = TBooleanNone() + or + // flow out of a callable + exists(DataFlowCall call | + nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and + fromArg = false + or + exists(boolean argStored0 | + nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and + nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + ) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} + /** + * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. + */ + pragma[noinline] + private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + Stage1::nodeCand1(node, unbind(config)) and + nodeCandFwd2(mid, _, _, stored, config) and + storeCand1(mid, c, node, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( + pragma[nomagic] + private predicate nodeCandFwd2Read( + Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config + ) { exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + nodeCandFwd2(mid, fromArg, argStored, true, config) and + read(mid, c, node, config) ) - or - exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + } + + pragma[nomagic] + private predicate nodeCandFwd2In( + DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + nodeCandFwd2(arg, fromArg, argStored, stored, config) and + flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) + | + stored = false or allowsFieldFlow = true ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and - toReturn = false and - returnRead = TBooleanNone() + } + + pragma[nomagic] + private predicate nodeCandFwd2Out( + DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, + Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow | + nodeCandFwd2(ret, fromArg, argStored, stored, config) and + flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) + | + stored = false or allowsFieldFlow = true ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and - toReturn = false and - returnRead = TBooleanNone() and - read = false + } + + pragma[nomagic] + private predicate nodeCandFwd2OutFromArg( + DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config + ) { + nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. + */ + pragma[nomagic] + private predicate nodeCandFwd2IsEntered( + DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, + Configuration config + ) { + exists(ParameterNode p | + nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and + parameterThroughFlowNodeCand1(p, config) ) + } + + /** + * Holds if `node` is part of a path from a source to a sink in the + * configuration `config`. The Boolean `read` records whether the tracked + * value must be read from a field of `node` in order to reach a sink. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink, and if so, `returnRead` + * records whether a field must be read from the returned value. + */ + predicate nodeCand2( + Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config + ) { + nodeCandFwd2(node, _, _, false, config) and + config.isSink(node) and + toReturn = false and + returnRead = TBooleanNone() and + read = false or - // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and + ( + exists(Node mid | + localFlowStepNodeCand1(node, mid, config) and + nodeCand2(mid, toReturn, returnRead, read, config) + ) + or + exists(Node mid | + additionalLocalFlowStepNodeCand1(node, mid, config) and + nodeCand2(mid, toReturn, returnRead, read, config) and + read = false + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand2(mid, _, _, read, config) and + toReturn = false and + returnRead = TBooleanNone() + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand2(mid, _, _, read, config) and + toReturn = false and + returnRead = TBooleanNone() and + read = false + ) + or + // store + exists(Content c | + nodeCand2Store(c, node, toReturn, returnRead, read, config) and + nodeCand2IsRead(c, read, config) + ) + or + // read + exists(Node mid, Content c, boolean read0 | + read(node, c, mid, config) and + nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and + nodeCand2(mid, toReturn, returnRead, read0, config) and + read = true + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand2In(call, node, toReturn, returnRead, read, config) and + toReturn = false + or + exists(boolean returnRead0 | + nodeCand2InToReturn(call, node, returnRead0, read, config) and + nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + ) + ) + or + // flow out of a callable + nodeCand2Out(_, node, _, _, read, config) and + toReturn = true and + if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) + then returnRead = TBooleanSome(read) + else returnRead = TBooleanNone() ) - or - // read - exists(Node mid, Content c, boolean read0 | + } + + /** + * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. + */ + pragma[noinline] + private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + nodeCandFwd2(node, _, _, true, unbind(config)) and read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and + nodeCand2(mid, _, _, read, config) ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and - toReturn = false - or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) - ) + } + + pragma[nomagic] + private predicate nodeCand2Store( + Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, + Configuration config + ) { + exists(Node mid | + storeCand1(node, c, mid, config) and + nodeCand2(mid, toReturn, returnRead, true, config) and + nodeCandFwd2(node, _, _, stored, unbind(config)) ) - or - // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and - toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + /** + * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. + */ + pragma[nomagic] + private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { + exists(Node node | + nodeCand2Store(c, node, _, _, stored, conf) and + nodeCand2(node, _, _, stored, conf) + ) + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * Holds if `c` is the target of both a store and a read in the path graph + * covered by `nodeCand2`. + */ + pragma[noinline] + predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { + exists(boolean apNonEmpty | + nodeCand2IsStored(c, apNonEmpty, conf) and + nodeCand2IsRead(c, apNonEmpty, conf) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate nodeCand2Out( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + nodeCand2(out, toReturn, returnRead, read, config) and + flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) + | + read = false or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate nodeCand2In( + DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + nodeCand2(p, toReturn, returnRead, read, config) and + flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) + | + read = false or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate nodeCand2InToReturn( + DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config + ) { + nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. + */ + pragma[nomagic] + private predicate nodeCand2IsReturned( + DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, + Configuration config + ) { + exists(ReturnNodeExt ret | + nodeCand2Out(call, ret, toReturn, returnRead, read, config) and + nodeCandFwd2(ret, true, TBooleanSome(_), read, config) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) + predicate nodeCand2(Node node, Configuration config) { + nodeCand2(node, _, _, _, config) + } } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} - -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } - pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::nodeCand2(node2, config) and + Stage2::nodeCand2(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1082,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::nodeCand2(node2, config) and + Stage2::nodeCand2(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1103,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::nodeCand2(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1121,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::nodeCand2(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1138,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::nodeCand2(node1, _, _, false, config) and + Stage2::nodeCand2(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1169,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::nodeCand2(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::nodeCand2(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1184,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::nodeCand2(node2, unbind(config)) ) ) } @@ -1196,9 +1208,9 @@ private import LocalFlowBigStep pragma[nomagic] private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) + Stage2::nodeCand2(node1, _, _, true, unbind(config)) and + Stage2::nodeCand2(node2, config) and + Stage2::nodeCand2IsReadAndStored(c, unbind(config)) } pragma[nomagic] @@ -1206,345 +1218,347 @@ private predicate storeCand2( Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config ) { store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) + Stage2::nodeCand2(node1, config) and + Stage2::nodeCand2(node2, _, _, true, unbind(config)) and + Stage2::nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) } -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} +private module Stage3 { + /** + * Holds if `node` is reachable with access path front `apf` from a + * source in the configuration `config`. + * + * The Boolean `fromArg` records whether the node is reached through an + * argument in a call, and if so, `argApf` records the front of the + * access path of that argument. + */ + pragma[nomagic] + predicate flowCandFwd( + Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, + Configuration config + ) { + flowCandFwd0(node, fromArg, argApf, apf, config) and + not apf.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() + } -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and + pragma[nomagic] + private predicate flowCandFwd0( + Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, + Configuration config + ) { + Stage2::nodeCand2(node, _, _, false, config) and + config.isSource(node) and fromArg = false and argApf = TAccessPathFrontNone() and apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid | + flowCandFwd(mid, fromArg, argApf, apf, config) and + localFlowBigStep(mid, node, true, _, config, _) ) - ) -} + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(mid, fromArg, argApf, nil, config) and + localFlowBigStep(mid, node, false, apf, config, _) + ) + or + exists(Node mid | + flowCandFwd(mid, _, _, apf, config) and + Stage2::nodeCand2(node, unbind(config)) and + jumpStep(mid, node, config) and + fromArg = false and + argApf = TAccessPathFrontNone() + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(mid, _, _, nil, config) and + Stage2::nodeCand2(node, unbind(config)) and + additionalJumpStep(mid, node, config) and + fromArg = false and + argApf = TAccessPathFrontNone() and + apf = TFrontNil(getNodeType(node)) + ) + or + // store + exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | + flowCandFwd(mid, fromArg, argApf, apf0, config) and + storeCand2(mid, tc, node, contentType, config) and + Stage2::nodeCand2(node, _, _, true, unbind(config)) and + apf.headUsesContent(tc) and + compatibleTypes(apf0.getType(), contentType) + ) + or + // read + exists(TypedContent tc | + flowCandFwdRead(tc, node, fromArg, argApf, config) and + flowCandFwdConsCand(tc, apf, config) and + Stage2::nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) + ) + or + // flow into a callable + flowCandFwdIn(_, node, _, _, apf, config) and + fromArg = true and + if Stage2::nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) + then argApf = TAccessPathFrontSome(apf) + else argApf = TAccessPathFrontNone() + or + // flow out of a callable + exists(DataFlowCall call | + flowCandFwdOut(call, node, fromArg, argApf, apf, config) and + fromArg = false + or + exists(AccessPathFront argApf0 | + flowCandFwdOutFromArg(call, node, argApf0, apf, config) and + flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + ) + ) + } -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} + pragma[nomagic] + private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { + exists(Node mid, Node n, DataFlowType contentType | + flowCandFwd(mid, _, _, apf, config) and + storeCand2(mid, tc, n, contentType, config) and + Stage2::nodeCand2(n, _, _, true, unbind(config)) and + compatibleTypes(apf.getType(), contentType) + ) + } -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} + pragma[nomagic] + private predicate flowCandFwdRead0( + Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, + AccessPathFrontOption argApf, AccessPathFrontHead apf, Configuration config + ) { + flowCandFwd(node1, fromArg, argApf, apf, config) and + readCand2(node1, c, node2, config) and + apf.headUsesContent(tc) + } -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} + pragma[nomagic] + private predicate flowCandFwdRead( + TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config + ) { + flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) + } -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowCandFwdIn( + DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, + AccessPathFront apf, Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + flowCandFwd(arg, fromArg, argApf, apf, config) and + flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + | + apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowCandFwdOut( + DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, + AccessPathFront apf, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow | + flowCandFwd(ret, fromArg, argApf, apf, config) and + flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) + | + apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} + pragma[nomagic] + private predicate flowCandFwdOutFromArg( + DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config + ) { + flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} + /** + * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. + */ + pragma[nomagic] + private predicate flowCandFwdIsEntered( + DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, + Configuration config + ) { + exists(ParameterNode p | + flowCandFwdIn(call, p, fromArg, argApf, apf, config) and + Stage2::nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) + ) + } -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} + /** + * Holds if `node` with access path front `apf` 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, and if so, `returnApf` + * records the front of the access path of the returned value. + */ + pragma[nomagic] + predicate flowCand( + Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, + Configuration config + ) { + flowCand0(node, toReturn, returnApf, apf, config) and + flowCandFwd(node, _, _, apf, config) + } -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | + pragma[nomagic] + private predicate flowCand0( + Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, + Configuration config + ) { flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and + config.isSink(node) and toReturn = false and returnApf = TAccessPathFrontNone() and apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + localFlowBigStep(node, mid, true, _, config, _) and + flowCand(mid, toReturn, returnApf, apf, config) ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(node, _, _, apf, config) and + localFlowBigStep(node, mid, false, _, config, _) and + flowCand(mid, toReturn, returnApf, nil, config) and + apf instanceof AccessPathFrontNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + flowCand(mid, _, _, apf, config) and + toReturn = false and + returnApf = TAccessPathFrontNone() + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(node, _, _, apf, config) and + additionalJumpStep(node, mid, config) and + flowCand(mid, _, _, nil, config) and + toReturn = false and + returnApf = TAccessPathFrontNone() and + apf instanceof AccessPathFrontNil + ) + or + // store + exists(TypedContent tc | + flowCandStore(node, tc, apf, toReturn, returnApf, config) and + flowCandConsCand(tc, apf, config) + ) + or + // read + exists(TypedContent tc, AccessPathFront apf0 | + flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and + flowCandFwdConsCand(tc, apf0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + flowCandIn(call, node, toReturn, returnApf, apf, config) and + toReturn = false + or + exists(AccessPathFront returnApf0 | + flowCandInToReturn(call, node, returnApf0, apf, config) and + flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + ) + ) + or + // flow out of a callable + flowCandOut(_, node, _, _, apf, config) and + toReturn = true and + if flowCandFwd(node, true, _, apf, config) + then returnApf = TAccessPathFrontSome(apf) + else returnApf = TAccessPathFrontNone() + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + predicate readCandFwd( + Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config + ) { + flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + pragma[nomagic] + private predicate flowCandRead( + Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, + AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config + ) { + exists(Node mid | + readCandFwd(node, tc, apf, mid, config) and + flowCand(mid, toReturn, returnApf, apf0, config) + ) + } -pragma[nomagic] -private predicate flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate flowCandStore( + Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, + AccessPathFrontOption returnApf, Configuration config + ) { + exists(Node mid | + flowCandFwd(node, _, _, apf, config) and + storeCand2(node, tc, mid, _, unbind(config)) and + flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) + ) + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { + flowCandFwdConsCand(tc, apf, config) and + flowCandRead(_, tc, _, _, _, apf, config) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowCandOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, + AccessPathFront apf, Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + flowCand(out, toReturn, returnApf, apf, config) and + flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) + | + apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowCandIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, + AccessPathFront apf, Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + flowCand(p, toReturn, returnApf, apf, config) and + flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + | + apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config) -} + pragma[nomagic] + private predicate flowCandInToReturn( + DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, + Configuration config + ) { + flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + /** + * Holds if an output from `call` is reached in the flow covered by `flowCand`. + */ + pragma[nomagic] + private predicate flowCandIsReturned( + DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, + Configuration config + ) { + exists(ReturnNodeExt ret | + flowCandOut(call, ret, toReturn, returnApf, apf, config) and + flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) + ) + } } /** @@ -1553,8 +1567,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::flowCand(node, true, _, apf, config) and + Stage3::flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,10 +1578,10 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::flowCandConsCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) or flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) ) and @@ -1580,11 +1594,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::flowCandConsCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::flowCandConsCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +1728,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::flowCandConsCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +1739,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::flowCandConsCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,411 +1764,415 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + /** + * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path + * of that argument. + */ + predicate flowFwd( + Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, + AccessPathApprox apa, Configuration config + ) { + flowFwd0(node, cc, argApa, apf, apa, config) and + Stage3::flowCand(node, _, _, apf, config) + } -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + private predicate flowFwd0( + Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, + AccessPathApprox apa, Configuration config + ) { + Stage3::flowCand(node, _, _, _, config) and + config.isSource(node) and + cc instanceof CallContextAny and + argApa = TAccessPathApproxNone() and + apa = TNil(getNodeType(node)) and + apf = apa.(AccessPathApproxNil).getFront() or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() - ) - or - exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and - jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and - additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) - ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and + Stage3::flowCand(node, _, _, _, unbind(config)) and ( - resolveReturn(innercc, innerc, call) + exists(Node mid, LocalCallContext localCC | + flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and + localFlowBigStep(mid, node, true, _, config, localCC) + ) or - innercc.(CallContextCall).matchesCall(call) + exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | + flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and + localFlowBigStep(mid, node, false, apf, config, localCC) and + apf = apa.(AccessPathApproxNil).getFront() + ) + or + exists(Node mid | + flowFwd(mid, _, _, apf, apa, config) and + jumpStep(mid, node, config) and + cc instanceof CallContextAny and + argApa = TAccessPathApproxNone() + ) + or + exists(Node mid, AccessPathApproxNil nil | + flowFwd(mid, _, _, _, nil, config) and + additionalJumpStep(mid, node, config) and + cc instanceof CallContextAny and + argApa = TAccessPathApproxNone() and + apa = TNil(getNodeType(node)) and + apf = apa.(AccessPathApproxNil).getFront() + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + or + // store + exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) + or + // read + exists(TypedContent tc | + flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and + flowFwdConsCand(tc, apf, apa, config) + ) + or + // flow into a callable + flowFwdIn(_, node, _, cc, _, apf, apa, config) and + if Stage3::flowCand(node, true, _, apf, config) + then argApa = TAccessPathApproxSome(apa) + else argApa = TAccessPathApproxNone() + or + // flow out of a callable + exists(DataFlowCall call | + exists(DataFlowCallable c | + flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + or + exists(AccessPathApprox argApa0 | + flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and + flowFwdIsEntered(call, cc, argApa, argApa0, config) + ) + ) + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate flowFwdLocalEntry( + Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, + AccessPathApprox apa, LocalCallContext localCC, Configuration config + ) { + flowFwd(node, cc, argApa, apf, apa, config) and + localFlowEntry(node, config) and + localCC = getLocalCallContext(cc, node.getEnclosingCallable()) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + pragma[nomagic] + private predicate flowFwdStore( + Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, + AccessPathApproxOption argApa, Configuration config + ) { + exists(Node mid, AccessPathFront apf0 | + flowFwd(mid, cc, argApa, apf0, apa0, config) and + flowFwdStore0(mid, tc, node, apf0, apf, config) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate storeCand( + Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, + Configuration config + ) { + storeCand2(mid, tc, node, _, config) and + Stage3::flowCand(mid, _, _, apf0, config) and + apf.headUsesContent(tc) + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | + pragma[noinline] + private predicate flowFwdStore0( + Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, + Configuration config + ) { + storeCand(mid, tc, node, apf0, apf, config) and + Stage3::flowCandConsCand(tc, apf0, config) and + Stage3::flowCand(node, _, _, apf, unbind(config)) + } + + pragma[nomagic] + private predicate flowFwdRead0( + Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, + CallContext cc, AccessPathApproxOption argApa, Configuration config + ) { + flowFwd(node1, cc, argApa, apf0, apa0, config) and + Stage3::readCandFwd(node1, tc, apf0, node2, config) + } + + pragma[nomagic] + private predicate flowFwdRead( + Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, + AccessPathApproxOption argApa, Configuration config + ) { + exists(Node mid, TypedContent tc | + flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and + Stage3::flowCand(node, _, _, apf, unbind(config)) and + Stage3::flowCandConsCand(tc, apf, unbind(config)) + ) + } + + pragma[nomagic] + private predicate flowFwdConsCand( + TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config + ) { + exists(Node n | + flowFwd(n, _, _, apf, apa, config) and + flowFwdStore0(n, tc, _, apf, _, config) + ) + } + + pragma[nomagic] + private predicate flowFwdIn( + DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, + AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | + flowFwd(arg, outercc, argApa, apf, apa, config) and + flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and + c = p.getEnclosingCallable() and + c = resolveCall(call, outercc) and + Stage3::flowCand(p, _, _, _, unbind(config)) and + if recordDataFlowCallSite(call, c) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + | + apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate flowFwdOut( + DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, + AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow | + flowFwd(ret, innercc, argApa, apf, apa, config) and + flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and + innerc = ret.getEnclosingCallable() and + Stage3::flowCand(node, _, _, _, unbind(config)) and + ( + resolveReturn(innercc, innerc, call) + or + innercc.(CallContextCall).matchesCall(call) + ) + | + apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate flowFwdOutFromArg( + DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, + AccessPathApprox apa, Configuration config + ) { + flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, + config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. + */ + pragma[nomagic] + private predicate flowFwdIsEntered( + DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, + Configuration config + ) { + exists(ParameterNode p, AccessPathFront apf | + flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and + Stage3::flowCand(p, true, TAccessPathFrontSome(_), apf, config) + ) + } + + /** + * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` + * records the approximate access path of the returned value. + */ + predicate flow( + Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, + Configuration config + ) { + flow0(node, toReturn, returnApa, apa, config) and + flowFwd(node, _, _, _, apa, config) + } + + private predicate flow0( + Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, + Configuration config + ) { flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and + config.isSink(node) and toReturn = false and returnApa = TAccessPathApproxNone() and apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localFlowBigStep(node, mid, true, _, config, _) and + flow(mid, toReturn, returnApa, apa, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, AccessPathApproxNil nil | + flowFwd(node, _, _, _, apa, config) and + localFlowBigStep(node, mid, false, _, config, _) and + flow(mid, toReturn, returnApa, nil, config) and + apa instanceof AccessPathApproxNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + flow(mid, _, _, apa, config) and + toReturn = false and + returnApa = TAccessPathApproxNone() + ) + or + exists(Node mid, AccessPathApproxNil nil | + flowFwd(node, _, _, _, apa, config) and + additionalJumpStep(node, mid, config) and + flow(mid, _, _, nil, config) and + toReturn = false and + returnApa = TAccessPathApproxNone() and + apa instanceof AccessPathApproxNil + ) + or + // store + exists(TypedContent tc | + flowStore(tc, node, toReturn, returnApa, apa, config) and + flowConsCand(tc, apa, config) + ) + or + // read + exists(Node mid, AccessPathApprox apa0 | + readFlowFwd(node, _, mid, apa, apa0, config) and + flow(mid, toReturn, returnApa, apa0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + flowIn(call, node, toReturn, returnApa, apa, config) and + toReturn = false + or + exists(AccessPathApprox returnApa0 | + flowInToReturn(call, node, returnApa0, apa, config) and + flowIsReturned(call, toReturn, returnApa, returnApa0, config) + ) + ) + or + // flow out of a callable + flowOut(_, node, _, _, apa, config) and + toReturn = true and + if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) + then returnApa = TAccessPathApproxSome(apa) + else returnApa = TAccessPathApproxNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate storeFlowFwd( + Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, + Configuration config + ) { + storeCand2(node1, tc, node2, _, config) and + flowFwdStore(node2, tc, apa, _, _, _, config) and + apa0 = push(tc, apa) + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + pragma[nomagic] + private predicate flowStore( + TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, + AccessPathApprox apa, Configuration config + ) { + exists(Node mid, AccessPathApprox apa0 | + storeFlowFwd(node, tc, mid, apa, apa0, config) and + flow(mid, toReturn, returnApa, apa0, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate readFlowFwd( + Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, + Configuration config + ) { + exists(AccessPathFrontHead apf | + Stage3::readCandFwd(node1, tc, apf, node2, config) and + flowFwdRead(node2, apf, apa, _, _, _, config) and + apa0 = pop(tc, apa) and + flowFwdConsCand(tc, _, apa0, unbind(config)) + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { + exists(Node n, Node mid | + flow(mid, _, _, apa, config) and + readFlowFwd(n, tc, mid, _, apa, config) + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, + AccessPathApprox apa, Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + flow(out, toReturn, returnApa, apa, config) and + flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) + | + apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate flowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, + AccessPathApprox apa, Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + flow(p, toReturn, returnApa, apa, config) and + flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + | + apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + private predicate flowInToReturn( + DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, + Configuration config + ) { + flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + /** + * Holds if an output from `call` is reached in the flow covered by `flow`. + */ + pragma[nomagic] + private predicate flowIsReturned( + DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, + Configuration config + ) { + exists(ReturnNodeExt ret, CallContextCall ccc | + flowOut(call, ret, toReturn, returnApa, apa, config) and + flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and + ccc.matchesCall(call) + ) + } + + predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - pragma[noinline] private predicate parameterFlow( ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, Configuration config ) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and + Stage4::flow(p, true, TAccessPathApproxSome(apa0), apa, config) and c = p.getEnclosingCallable() } @@ -2162,16 +2180,16 @@ private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, A exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | parameterFlow(p, apa, apa0, c, config) and c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) + Stage4::flow(ret, true, TAccessPathApproxSome(_), apa0, config) and + Stage4::flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) ) } private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::flow(n, true, _, apa0, config) and + Stage4::flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and n.getEnclosingCallable() = c ) } @@ -2222,14 +2240,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) + Stage4::flowConsCand(tc, + any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2268,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::flowConsCand(head, result, config) ) } @@ -2323,7 +2342,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::flow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2352,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::flow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::flow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2486,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::flowConsCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2517,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2734,8 +2753,8 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pragma[nomagic] private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) + Stage3::readCandFwd(node1, tc, _, node2, config) and + Stage4::flow(node2, config) } pragma[nomagic] @@ -2750,7 +2769,7 @@ private predicate pathReadStep( pragma[nomagic] private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { storeCand2(node1, tc, node2, _, config) and - flow(node2, config) + Stage4::flow(node2, config) } pragma[nomagic] @@ -2793,7 +2812,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::flow(result, _, _, apa, config) } /** @@ -2829,7 +2848,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::flow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } From f3f968ce6db1b33fe388ae092430b2ef1ad24dcd Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 19 Oct 2020 13:05:08 +0200 Subject: [PATCH 12/97] Dataflow: Rename predicates. --- .../java/dataflow/internal/DataFlowImpl.qll | 720 +++++++++--------- 1 file changed, 356 insertions(+), 364 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index d01a7e6be25..664f89f469f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -278,30 +278,30 @@ private module Stage1 { * The Boolean `fromArg` records whether the node is reached through an * argument in a call. */ - predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { + predicate fwdFlow(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( config.isSource(node) and fromArg = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and + fwdFlow(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and + fwdFlow(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + fwdFlow(mid, config) and jumpStep(mid, node, config) and fromArg = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and + fwdFlow(mid, config) and additionalJumpStep(mid, node, config) and fromArg = false ) @@ -309,92 +309,88 @@ private module Stage1 { // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and + fwdFlow(mid, fromArg, config) and store(mid, _, node, _) and not outBarrier(mid, config) ) or // read exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and + fwdFlowRead(c, node, fromArg, config) and + fwdFlowIsStored(c, config) and not inBarrier(node, config) ) or // flow into a callable exists(Node arg | - nodeCandFwd1(arg, config) and + fwdFlow(arg, config) and viableParamArg(_, node, arg) and fromArg = true ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and + fwdFlowOut(call, node, false, config) and fromArg = false or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, fromArg, config) ) ) } - private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } pragma[nomagic] - private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { + private predicate fwdFlowRead(Content c, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and + fwdFlow(mid, fromArg, config) and read(mid, c, node) ) } /** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate nodeCandFwd1IsStored(Content c, Configuration config) { + private predicate fwdFlowIsStored(Content c, Configuration config) { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - nodeCandFwd1(mid, config) and + fwdFlow(mid, config) and store(mid, tc, node, _) and c = tc.getContent() ) } pragma[nomagic] - private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config - ) { + private predicate fwdFlowReturnPosition(ReturnPosition pos, boolean fromArg, Configuration config) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and + fwdFlow(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] - private predicate nodeCandFwd1Out( - DataFlowCall call, Node out, boolean fromArg, Configuration config - ) { + private predicate fwdFlowOut(DataFlowCall call, Node out, boolean fromArg, Configuration config) { exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + fwdFlowReturnPosition(pos, fromArg, config) and viableReturnPosOut(call, pos, out) ) } pragma[nomagic] - private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) } /** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { + private predicate fwdFlowIsEntered(DataFlowCall call, boolean fromArg, Configuration config) { exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and + fwdFlow(arg, fromArg, config) and viableParamArg(call, _, arg) ) } @@ -407,88 +403,88 @@ private module Stage1 { * the enclosing callable in order to reach a sink. */ pragma[nomagic] - predicate nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow_0(node, toReturn, config) and + fwdFlow(node, config) } pragma[nomagic] - private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and + private predicate revFlow_0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and config.isSink(node) and toReturn = false or exists(Node mid | localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) + revFlow(mid, toReturn, config) ) or exists(Node mid | additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) + revFlow(mid, toReturn, config) ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and + revFlow(mid, _, config) and toReturn = false ) or exists(Node mid | additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and + revFlow(mid, _, config) and toReturn = false ) or // store exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) + revFlowStore(c, node, toReturn, config) and + revFlowIsRead(c, config) ) or // read exists(Node mid, Content c | read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) + fwdFlowIsStored(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and + revFlowIn(call, node, false, config) and toReturn = false or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable exists(ReturnPosition pos | - nodeCand1Out(pos, config) and + revFlowOut(pos, config) and getReturnPosition(node) = pos and toReturn = true ) } /** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate nodeCand1IsRead(Content c, Configuration config) { + private predicate revFlowIsRead(Content c, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and + fwdFlow(node, unbind(config)) and read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) + fwdFlowIsStored(c, unbind(config)) and + revFlow(mid, _, config) ) } pragma[nomagic] - private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and + revFlow(mid, toReturn, config) and + fwdFlowIsStored(c, unbind(config)) and store(node, tc, mid, _) and c = tc.getContent() ) @@ -496,25 +492,25 @@ private module Stage1 { /** * Holds if `c` is the target of both a read and a store in the flow covered - * by `nodeCand1`. + * by `revFlow`. */ - predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) + predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowIsRead(c, conf) and + revFlowStore(c, _, _, conf) } pragma[nomagic] predicate viableReturnPosOutNodeCandFwd1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCandFwd1ReturnPosition(pos, _, config) and + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) } pragma[nomagic] - private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { + private predicate revFlowOut(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and + revFlow(out, _, config) and viableReturnPosOutNodeCandFwd1(call, pos, out, config) ) } @@ -524,47 +520,45 @@ private module Stage1 { DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) + fwdFlow(arg, config) } pragma[nomagic] - private predicate nodeCand1In( + private predicate revFlowIn( DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config ) { exists(ParameterNode p | - nodeCand1(p, toReturn, config) and + revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + * Holds if an output from `call` is reached in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) ) } pragma[nomagic] - predicate nodeCand1(Node node, Configuration config) { - nodeCand1(node, _, config) - } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } private predicate throughFlowNodeCand1(Node node, Configuration config) { - Stage1::nodeCand1(node, true, config) and - Stage1::nodeCandFwd1(node, true, config) and + Stage1::revFlow(node, true, config) and + Stage1::fwdFlow(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -600,8 +594,8 @@ private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration c pragma[nomagic] private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { exists(TypedContent tc | - Stage1::nodeCand1IsReadAndStored(c, config) and - Stage1::nodeCand1(n2, unbind(config)) and + Stage1::revFlowIsReadAndStored(c, config) and + Stage1::revFlow(n2, unbind(config)) and store(n1, tc, n2, _) and c = tc.getContent() ) @@ -609,20 +603,20 @@ private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) pragma[nomagic] private predicate read(Node n1, Content c, Node n2, Configuration config) { - Stage1::nodeCand1IsReadAndStored(c, config) and - Stage1::nodeCand1(n2, unbind(config)) and + Stage1::revFlowIsReadAndStored(c, config) and + Stage1::revFlow(n2, unbind(config)) and read(n1, c, n2) } pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - Stage1::nodeCand1(node1, config) and + Stage1::revFlow(node1, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - Stage1::nodeCand1(node1, config) and + Stage1::revFlow(node1, config) and additionalLocalFlowStep(node1, node2, config) } @@ -630,7 +624,7 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - Stage1::nodeCand1(out, config) and + Stage1::revFlow(out, config) and Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } @@ -644,7 +638,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - Stage1::nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -654,7 +648,7 @@ private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::nodeCand1(arg, config) + Stage1::revFlow(arg, config) } /** @@ -666,7 +660,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - Stage1::nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -746,37 +740,37 @@ private module Stage2 { * argument in a call, and if so, `argStored` records whether the tracked * value was stored into a field of the argument. */ - private predicate nodeCandFwd2( + private predicate fwdFlow( Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config ) { - Stage1::nodeCand1(node, config) and + Stage1::revFlow(node, config) and config.isSource(node) and fromArg = false and argStored = TBooleanNone() and stored = false or - Stage1::nodeCand1(node, unbind(config)) and + Stage1::revFlow(node, unbind(config)) and ( exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and + fwdFlow(mid, fromArg, argStored, stored, config) and localFlowStepNodeCand1(mid, node, config) ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and + fwdFlow(mid, fromArg, argStored, stored, config) and additionalLocalFlowStepNodeCand1(mid, node, config) and stored = false ) or exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, stored, config) and jumpStep(mid, node, config) and fromArg = false and argStored = TBooleanNone() ) or exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, stored, config) and additionalJumpStep(mid, node, config) and fromArg = false and argStored = TBooleanNone() and @@ -785,19 +779,19 @@ private module Stage2 { or // store exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and + fwdFlow(mid, fromArg, argStored, _, config) and storeCand1(mid, _, node, config) and stored = true ) or // read exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + fwdFlowRead(c, node, fromArg, argStored, config) and + fwdFlowIsStored(c, stored, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and + fwdFlowIn(_, node, _, _, stored, config) and fromArg = true and if parameterThroughFlowNodeCand1(node, config) then argStored = TBooleanSome(stored) @@ -805,47 +799,47 @@ private module Stage2 { or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and + fwdFlowOut(call, node, fromArg, argStored, stored, config) and fromArg = false or exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + fwdFlowOutFromArg(call, node, argStored0, stored, config) and + fwdFlowIsEntered(call, fromArg, argStored, argStored0, config) ) ) ) } /** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ pragma[noinline] - private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { + private predicate fwdFlowIsStored(Content c, boolean stored, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and - Stage1::nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and + Stage1::revFlow(node, unbind(config)) and + fwdFlow(mid, _, _, stored, config) and storeCand1(mid, c, node, config) ) } pragma[nomagic] - private predicate nodeCandFwd2Read( + private predicate fwdFlowRead( Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config ) { exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and + fwdFlow(mid, fromArg, argStored, true, config) and read(mid, c, node, config) ) } pragma[nomagic] - private predicate nodeCandFwd2In( + private predicate fwdFlowIn( DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and + fwdFlow(arg, fromArg, argStored, stored, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | stored = false or allowsFieldFlow = true @@ -853,12 +847,12 @@ private module Stage2 { } pragma[nomagic] - private predicate nodeCandFwd2Out( + private predicate fwdFlowOut( DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and + fwdFlow(ret, fromArg, argStored, stored, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | stored = false or allowsFieldFlow = true @@ -866,22 +860,22 @@ private module Stage2 { } pragma[nomagic] - private predicate nodeCandFwd2OutFromArg( + private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config ) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) + fwdFlowOut(call, out, true, TBooleanSome(argStored), stored, config) } /** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate nodeCandFwd2IsEntered( + private predicate fwdFlowIsEntered( DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config ) { exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and + fwdFlowIn(call, p, fromArg, argStored, stored, config) and parameterThroughFlowNodeCand1(p, config) ) } @@ -895,38 +889,38 @@ private module Stage2 { * the enclosing callable in order to reach a sink, and if so, `returnRead` * records whether a field must be read from the returned value. */ - predicate nodeCand2( + predicate revFlow( Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config ) { - nodeCandFwd2(node, _, _, false, config) and + fwdFlow(node, _, _, false, config) and config.isSink(node) and toReturn = false and returnRead = TBooleanNone() and read = false or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and + fwdFlow(node, _, _, unbindBool(read), unbind(config)) and ( exists(Node mid | localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + revFlow(mid, toReturn, returnRead, read, config) ) or exists(Node mid | additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and + revFlow(mid, toReturn, returnRead, read, config) and read = false ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, read, config) and toReturn = false and returnRead = TBooleanNone() ) or exists(Node mid | additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, read, config) and toReturn = false and returnRead = TBooleanNone() and read = false @@ -934,94 +928,94 @@ private module Stage2 { or // store exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + revFlowStore(c, node, toReturn, returnRead, read, config) and + revFlowIsRead(c, read, config) ) or // read exists(Node mid, Content c, boolean read0 | read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and + fwdFlowIsStored(c, unbindBool(read0), unbind(config)) and + revFlow(mid, toReturn, returnRead, read0, config) and read = true ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnRead, read, config) and toReturn = false or exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + revFlowInToReturn(call, node, returnRead0, read, config) and + revFlowIsReturned(call, toReturn, returnRead, returnRead0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, read, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) + if fwdFlow(node, true, TBooleanSome(_), unbindBool(read), config) then returnRead = TBooleanSome(read) else returnRead = TBooleanNone() ) } /** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. + * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[noinline] - private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { + private predicate revFlowIsRead(Content c, boolean read, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and + fwdFlow(node, _, _, true, unbind(config)) and read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) + fwdFlowIsStored(c, unbindBool(read), unbind(config)) and + revFlow(mid, _, _, read, config) ) } pragma[nomagic] - private predicate nodeCand2Store( + private predicate revFlowStore( Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, Configuration config ) { exists(Node mid | storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) + revFlow(mid, toReturn, returnRead, true, config) and + fwdFlow(node, _, _, stored, unbind(config)) ) } /** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. + * Holds if `c` is the target of a store in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { + private predicate revFlowIsStored(Content c, boolean stored, Configuration conf) { exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) + revFlowStore(c, node, _, _, stored, conf) and + revFlow(node, _, _, stored, conf) ) } /** * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. + * covered by `revFlow`. */ pragma[noinline] - predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { + predicate revFlowIsReadAndStored(Content c, Configuration conf) { exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) + revFlowIsStored(c, apNonEmpty, conf) and + revFlowIsRead(c, apNonEmpty, conf) ) } pragma[nomagic] - private predicate nodeCand2Out( + private predicate revFlowOut( DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config ) { exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and + revFlow(out, toReturn, returnRead, read, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | read = false or allowsFieldFlow = true @@ -1029,12 +1023,12 @@ private module Stage2 { } pragma[nomagic] - private predicate nodeCand2In( + private predicate revFlowIn( DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and + revFlow(p, toReturn, returnRead, read, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | read = false or allowsFieldFlow = true @@ -1042,29 +1036,27 @@ private module Stage2 { } pragma[nomagic] - private predicate nodeCand2InToReturn( + private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config ) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) + revFlowIn(call, arg, true, TBooleanSome(returnRead), read, config) } /** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. + * Holds if an output from `call` is reached in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate nodeCand2IsReturned( + private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config ) { exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) + revFlowOut(call, ret, toReturn, returnRead, read, config) and + fwdFlow(ret, true, TBooleanSome(_), read, config) ) } - predicate nodeCand2(Node node, Configuration config) { - nodeCand2(node, _, _, _, config) - } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } } pragma[nomagic] @@ -1072,8 +1064,8 @@ private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::nodeCand2(node2, config) and - Stage2::nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1082,8 +1074,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::nodeCand2(node2, config) and - Stage2::nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1103,7 +1095,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - Stage2::nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1121,7 +1113,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | Stage2::nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1138,8 +1130,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - Stage2::nodeCand2(node1, _, _, false, config) and - Stage2::nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1169,13 +1161,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - Stage2::nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - Stage2::nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1184,7 +1176,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - Stage2::nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1208,9 +1200,9 @@ private import LocalFlowBigStep pragma[nomagic] private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { read(node1, c, node2, config) and - Stage2::nodeCand2(node1, _, _, true, unbind(config)) and - Stage2::nodeCand2(node2, config) and - Stage2::nodeCand2IsReadAndStored(c, unbind(config)) + Stage2::revFlow(node1, _, _, true, unbind(config)) and + Stage2::revFlow(node2, config) and + Stage2::revFlowIsReadAndStored(c, unbind(config)) } pragma[nomagic] @@ -1218,9 +1210,9 @@ private predicate storeCand2( Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config ) { store(node1, tc, node2, contentType) and - Stage2::nodeCand2(node1, config) and - Stage2::nodeCand2(node2, _, _, true, unbind(config)) and - Stage2::nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) + Stage2::revFlow(node1, config) and + Stage2::revFlow(node2, _, _, true, unbind(config)) and + Stage2::revFlowIsReadAndStored(tc.getContent(), unbind(config)) } private module Stage3 { @@ -1233,47 +1225,47 @@ private module Stage3 { * access path of that argument. */ pragma[nomagic] - predicate flowCandFwd( + predicate fwdFlow( Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, Configuration config ) { - flowCandFwd0(node, fromArg, argApf, apf, config) and + fwdFlow0(node, fromArg, argApf, apf, config) and not apf.isClearedAt(node) and if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() } pragma[nomagic] - private predicate flowCandFwd0( + private predicate fwdFlow0( Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, Configuration config ) { - Stage2::nodeCand2(node, _, _, false, config) and + Stage2::revFlow(node, _, _, false, config) and config.isSource(node) and fromArg = false and argApf = TAccessPathFrontNone() and apf = TFrontNil(getNodeType(node)) or exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and + fwdFlow(mid, fromArg, argApf, apf, config) and localFlowBigStep(mid, node, true, _, config, _) ) or exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and + fwdFlow(mid, fromArg, argApf, nil, config) and localFlowBigStep(mid, node, false, apf, config, _) ) or exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - Stage2::nodeCand2(node, unbind(config)) and + fwdFlow(mid, _, _, apf, config) and + Stage2::revFlow(node, unbind(config)) and jumpStep(mid, node, config) and fromArg = false and argApf = TAccessPathFrontNone() ) or exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - Stage2::nodeCand2(node, unbind(config)) and + fwdFlow(mid, _, _, nil, config) and + Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and fromArg = false and argApf = TAccessPathFrontNone() and @@ -1282,73 +1274,73 @@ private module Stage3 { or // store exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and + fwdFlow(mid, fromArg, argApf, apf0, config) and storeCand2(mid, tc, node, contentType, config) and - Stage2::nodeCand2(node, _, _, true, unbind(config)) and + Stage2::revFlow(node, _, _, true, unbind(config)) and apf.headUsesContent(tc) and compatibleTypes(apf0.getType(), contentType) ) or // read exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - Stage2::nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) + fwdFlowRead(tc, node, fromArg, argApf, config) and + fwdFlowConsCand(tc, apf, config) and + Stage2::revFlow(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) ) or // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and + fwdFlowIn(_, node, _, _, apf, config) and fromArg = true and - if Stage2::nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) + if Stage2::revFlow(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) then argApf = TAccessPathFrontSome(apf) else argApf = TAccessPathFrontNone() or // flow out of a callable exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and + fwdFlowOut(call, node, fromArg, argApf, apf, config) and fromArg = false or exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + fwdFlowOutFromArg(call, node, argApf0, apf, config) and + fwdFlowIsEntered(call, fromArg, argApf, argApf0, config) ) ) } pragma[nomagic] - private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { + private predicate fwdFlowConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and + fwdFlow(mid, _, _, apf, config) and storeCand2(mid, tc, n, contentType, config) and - Stage2::nodeCand2(n, _, _, true, unbind(config)) and + Stage2::revFlow(n, _, _, true, unbind(config)) and compatibleTypes(apf.getType(), contentType) ) } pragma[nomagic] - private predicate flowCandFwdRead0( + private predicate fwdFlowRead0( Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, AccessPathFrontHead apf, Configuration config ) { - flowCandFwd(node1, fromArg, argApf, apf, config) and + fwdFlow(node1, fromArg, argApf, apf, config) and readCand2(node1, c, node2, config) and apf.headUsesContent(tc) } pragma[nomagic] - private predicate flowCandFwdRead( + private predicate fwdFlowRead( TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config ) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) + fwdFlowRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) } pragma[nomagic] - private predicate flowCandFwdIn( + private predicate fwdFlowIn( DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and + fwdFlow(arg, fromArg, argApf, apf, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | apf instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1356,12 +1348,12 @@ private module Stage3 { } pragma[nomagic] - private predicate flowCandFwdOut( + private predicate fwdFlowOut( DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and + fwdFlow(ret, fromArg, argApf, apf, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) | apf instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1369,23 +1361,23 @@ private module Stage3 { } pragma[nomagic] - private predicate flowCandFwdOutFromArg( + private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config ) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) + fwdFlowOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) } /** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate flowCandFwdIsEntered( + private predicate fwdFlowIsEntered( DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, Configuration config ) { exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - Stage2::nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) + fwdFlowIn(call, p, fromArg, argApf, apf, config) and + Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) ) } @@ -1398,20 +1390,20 @@ private module Stage3 { * records the front of the access path of the returned value. */ pragma[nomagic] - predicate flowCand( + predicate revFlow( Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, Configuration config ) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) + revFlow0(node, toReturn, returnApf, apf, config) and + fwdFlow(node, _, _, apf, config) } pragma[nomagic] - private predicate flowCand0( + private predicate revFlow0( Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, Configuration config ) { - flowCandFwd(node, _, _, apf, config) and + fwdFlow(node, _, _, apf, config) and config.isSink(node) and toReturn = false and returnApf = TAccessPathFrontNone() and @@ -1419,27 +1411,27 @@ private module Stage3 { or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) + revFlow(mid, toReturn, returnApf, apf, config) ) or exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and + fwdFlow(node, _, _, apf, config) and localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and + revFlow(mid, toReturn, returnApf, nil, config) and apf instanceof AccessPathFrontNil ) or exists(Node mid | jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and + revFlow(mid, _, _, apf, config) and toReturn = false and returnApf = TAccessPathFrontNone() ) or exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and + fwdFlow(node, _, _, apf, config) and additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and returnApf = TAccessPathFrontNone() and apf instanceof AccessPathFrontNil @@ -1447,31 +1439,31 @@ private module Stage3 { or // store exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) + revFlowStore(node, tc, apf, toReturn, returnApf, config) and + revFlowConsCand(tc, apf, config) ) or // read exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) + revFlowRead(node, tc, apf, toReturn, returnApf, apf0, config) and + fwdFlowConsCand(tc, apf0, config) ) or // flow into a callable exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and + revFlowIn(call, node, toReturn, returnApf, apf, config) and toReturn = false or exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + revFlowInToReturn(call, node, returnApf0, apf, config) and + revFlowIsReturned(call, toReturn, returnApf, returnApf0, config) ) ) or // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and + revFlowOut(_, node, _, _, apf, config) and toReturn = true and - if flowCandFwd(node, true, _, apf, config) + if fwdFlow(node, true, _, apf, config) then returnApf = TAccessPathFrontSome(apf) else returnApf = TAccessPathFrontNone() } @@ -1480,45 +1472,45 @@ private module Stage3 { predicate readCandFwd( Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config ) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) + fwdFlowRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) } pragma[nomagic] - private predicate flowCandRead( + private predicate revFlowRead( Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config ) { exists(Node mid | readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) + revFlow(mid, toReturn, returnApf, apf0, config) ) } pragma[nomagic] - private predicate flowCandStore( + private predicate revFlowStore( Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, AccessPathFrontOption returnApf, Configuration config ) { exists(Node mid | - flowCandFwd(node, _, _, apf, config) and + fwdFlow(node, _, _, apf, config) and storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) + revFlow(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) ) } pragma[nomagic] - predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) + predicate revFlowConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { + fwdFlowConsCand(tc, apf, config) and + revFlowRead(_, tc, _, _, _, apf, config) } pragma[nomagic] - private predicate flowCandOut( + private predicate revFlowOut( DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, Configuration config ) { exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and + revFlow(out, toReturn, returnApf, apf, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | apf instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1526,12 +1518,12 @@ private module Stage3 { } pragma[nomagic] - private predicate flowCandIn( + private predicate revFlowIn( DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and + revFlow(p, toReturn, returnApf, apf, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | apf instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1539,24 +1531,24 @@ private module Stage3 { } pragma[nomagic] - private predicate flowCandInToReturn( + private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, Configuration config ) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config) + revFlowIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config) } /** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. + * Holds if an output from `call` is reached in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate flowCandIsReturned( + private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, Configuration config ) { exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) + revFlowOut(call, ret, toReturn, returnApf, apf, config) and + fwdFlow(ret, true, TAccessPathFrontSome(_), apf, config) ) } } @@ -1567,8 +1559,8 @@ private module Stage3 { */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - Stage3::flowCand(node, true, _, apf, config) and - Stage3::flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1578,10 +1570,10 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage3::flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::revFlowConsCand(tc, apf, config)) and nodes = strictcount(Node n | - Stage3::flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) or flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) ) and @@ -1594,11 +1586,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - Stage3::flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::revFlowConsCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage3::flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::revFlowConsCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1728,7 +1720,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | Stage3::flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::revFlowConsCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1739,7 +1731,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - Stage3::flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::revFlowConsCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1773,47 +1765,47 @@ private module Stage4 { * argument in a call, and if so, `argApa` records the approximate access path * of that argument. */ - predicate flowFwd( + predicate fwdFlow( Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { - flowFwd0(node, cc, argApa, apf, apa, config) and - Stage3::flowCand(node, _, _, apf, config) + fwdFlow0(node, cc, argApa, apf, apa, config) and + Stage3::revFlow(node, _, _, apf, config) } - private predicate flowFwd0( + private predicate fwdFlow0( Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { - Stage3::flowCand(node, _, _, _, config) and + Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and cc instanceof CallContextAny and argApa = TAccessPathApproxNone() and apa = TNil(getNodeType(node)) and apf = apa.(AccessPathApproxNil).getFront() or - Stage3::flowCand(node, _, _, _, unbind(config)) and + Stage3::revFlow(node, _, _, _, unbind(config)) and ( exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and + fwdFlowLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and localFlowBigStep(mid, node, true, _, config, localCC) ) or exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and + fwdFlowLocalEntry(mid, cc, argApa, _, nil, localCC, config) and localFlowBigStep(mid, node, false, apf, config, localCC) and apf = apa.(AccessPathApproxNil).getFront() ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, apf, apa, config) and jumpStep(mid, node, config) and cc instanceof CallContextAny and argApa = TAccessPathApproxNone() ) or exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + fwdFlow(mid, _, _, _, nil, config) and additionalJumpStep(mid, node, config) and cc instanceof CallContextAny and argApa = TAccessPathApproxNone() and @@ -1823,52 +1815,52 @@ private module Stage4 { ) or // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) + exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) or // read exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) + fwdFlowRead(node, _, push(tc, apa), apf, cc, argApa, config) and + fwdFlowConsCand(tc, apf, apa, config) ) or // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if Stage3::flowCand(node, true, _, apf, config) + fwdFlowIn(_, node, _, cc, _, apf, apa, config) and + if Stage3::revFlow(node, true, _, apf, config) then argApa = TAccessPathApproxSome(apa) else argApa = TAccessPathApproxNone() or // flow out of a callable exists(DataFlowCall call | exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and + fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) or exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + fwdFlowOutFromArg(call, node, argApa0, apf, apa, config) and + fwdFlowIsEntered(call, cc, argApa, argApa0, config) ) ) } pragma[nomagic] - private predicate flowFwdLocalEntry( + private predicate fwdFlowLocalEntry( Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, LocalCallContext localCC, Configuration config ) { - flowFwd(node, cc, argApa, apf, apa, config) and + fwdFlow(node, cc, argApa, apf, apa, config) and localFlowEntry(node, config) and localCC = getLocalCallContext(cc, node.getEnclosingCallable()) } pragma[nomagic] - private predicate flowFwdStore( + private predicate fwdFlowStore( Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, AccessPathApproxOption argApa, Configuration config ) { exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) + fwdFlow(mid, cc, argApa, apf0, apa0, config) and + fwdFlowStore0(mid, tc, node, apf0, apf, config) ) } @@ -1878,62 +1870,62 @@ private module Stage4 { Configuration config ) { storeCand2(mid, tc, node, _, config) and - Stage3::flowCand(mid, _, _, apf0, config) and + Stage3::revFlow(mid, _, _, apf0, config) and apf.headUsesContent(tc) } pragma[noinline] - private predicate flowFwdStore0( + private predicate fwdFlowStore0( Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, Configuration config ) { storeCand(mid, tc, node, apf0, apf, config) and - Stage3::flowCandConsCand(tc, apf0, config) and - Stage3::flowCand(node, _, _, apf, unbind(config)) + Stage3::revFlowConsCand(tc, apf0, config) and + Stage3::revFlow(node, _, _, apf, unbind(config)) } pragma[nomagic] - private predicate flowFwdRead0( + private predicate fwdFlowRead0( Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, CallContext cc, AccessPathApproxOption argApa, Configuration config ) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and + fwdFlow(node1, cc, argApa, apf0, apa0, config) and Stage3::readCandFwd(node1, tc, apf0, node2, config) } pragma[nomagic] - private predicate flowFwdRead( + private predicate fwdFlowRead( Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, AccessPathApproxOption argApa, Configuration config ) { exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - Stage3::flowCand(node, _, _, apf, unbind(config)) and - Stage3::flowCandConsCand(tc, apf, unbind(config)) + fwdFlowRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and + Stage3::revFlow(node, _, _, apf, unbind(config)) and + Stage3::revFlowConsCand(tc, apf, unbind(config)) ) } pragma[nomagic] - private predicate flowFwdConsCand( + private predicate fwdFlowConsCand( TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) + fwdFlow(n, _, _, apf, apa, config) and + fwdFlowStore0(n, tc, _, apf, _, config) ) } pragma[nomagic] - private predicate flowFwdIn( + private predicate fwdFlowIn( DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and + fwdFlow(arg, outercc, argApa, apf, apa, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and c = p.getEnclosingCallable() and c = resolveCall(call, outercc) and - Stage3::flowCand(p, _, _, _, unbind(config)) and + Stage3::revFlow(p, _, _, _, unbind(config)) and if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() @@ -1943,15 +1935,15 @@ private module Stage4 { } pragma[nomagic] - private predicate flowFwdOut( + private predicate fwdFlowOut( DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and + fwdFlow(ret, innercc, argApa, apf, apa, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and innerc = ret.getEnclosingCallable() and - Stage3::flowCand(node, _, _, _, unbind(config)) and + Stage3::revFlow(node, _, _, _, unbind(config)) and ( resolveReturn(innercc, innerc, call) or @@ -1963,25 +1955,25 @@ private module Stage4 { } pragma[nomagic] - private predicate flowFwdOutFromArg( + private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config ) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, + fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, config) } /** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate flowFwdIsEntered( + private predicate fwdFlowIsEntered( DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - Stage3::flowCand(p, true, TAccessPathFrontSome(_), apf, config) + fwdFlowIn(call, p, cc, _, argApa, apf, apa, config) and + Stage3::revFlow(p, true, TAccessPathFrontSome(_), apf, config) ) } @@ -1993,19 +1985,19 @@ private module Stage4 { * the enclosing callable in order to reach a sink, and if so, `returnApa` * records the approximate access path of the returned value. */ - predicate flow( + predicate revFlow( Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) + revFlow0(node, toReturn, returnApa, apa, config) and + fwdFlow(node, _, _, _, apa, config) } - private predicate flow0( + private predicate revFlow0( Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { - flowFwd(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, apa, config) and config.isSink(node) and toReturn = false and returnApa = TAccessPathApproxNone() and @@ -2013,27 +2005,27 @@ private module Stage4 { or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) + revFlow(mid, toReturn, returnApa, apa, config) ) or exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, apa, config) and localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and + revFlow(mid, toReturn, returnApa, nil, config) and apa instanceof AccessPathApproxNil ) or exists(Node mid | jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + revFlow(mid, _, _, apa, config) and toReturn = false and returnApa = TAccessPathApproxNone() ) or exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, apa, config) and additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and returnApa = TAccessPathApproxNone() and apa instanceof AccessPathApproxNil @@ -2041,31 +2033,31 @@ private module Stage4 { or // store exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) + revFlowStore(tc, node, toReturn, returnApa, apa, config) and + revFlowConsCand(tc, apa, config) ) or // read exists(Node mid, AccessPathApprox apa0 | readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) + revFlow(mid, toReturn, returnApa, apa0, config) ) or // flow into a callable exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and + revFlowIn(call, node, toReturn, returnApa, apa, config) and toReturn = false or exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + revFlowInToReturn(call, node, returnApa0, apa, config) and + revFlowIsReturned(call, toReturn, returnApa, returnApa0, config) ) ) or // flow out of a callable - flowOut(_, node, _, _, apa, config) and + revFlowOut(_, node, _, _, apa, config) and toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) + if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) then returnApa = TAccessPathApproxSome(apa) else returnApa = TAccessPathApproxNone() } @@ -2076,18 +2068,18 @@ private module Stage4 { Configuration config ) { storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and + fwdFlowStore(node2, tc, apa, _, _, _, config) and apa0 = push(tc, apa) } pragma[nomagic] - private predicate flowStore( + private predicate revFlowStore( TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { exists(Node mid, AccessPathApprox apa0 | storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) + revFlow(mid, toReturn, returnApa, apa0, config) ) } @@ -2098,27 +2090,27 @@ private module Stage4 { ) { exists(AccessPathFrontHead apf | Stage3::readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and + fwdFlowRead(node2, apf, apa, _, _, _, config) and apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) + fwdFlowConsCand(tc, _, apa0, unbind(config)) ) } pragma[nomagic] - predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { + predicate revFlowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { exists(Node n, Node mid | - flow(mid, _, _, apa, config) and + revFlow(mid, _, _, apa, config) and readFlowFwd(n, tc, mid, _, apa, config) ) } pragma[nomagic] - private predicate flowOut( + private predicate revFlowOut( DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and + revFlow(out, toReturn, returnApa, apa, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | apa instanceof AccessPathApproxNil or allowsFieldFlow = true @@ -2126,12 +2118,12 @@ private module Stage4 { } pragma[nomagic] - private predicate flowIn( + private predicate revFlowIn( DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and + revFlow(p, toReturn, returnApa, apa, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | apa instanceof AccessPathApproxNil or allowsFieldFlow = true @@ -2139,29 +2131,29 @@ private module Stage4 { } pragma[nomagic] - private predicate flowInToReturn( + private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, Configuration config ) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) + revFlowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) } /** - * Holds if an output from `call` is reached in the flow covered by `flow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate flowIsReturned( + private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, Configuration config ) { exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and + revFlowOut(call, ret, toReturn, returnApa, apa, config) and + fwdFlow(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and ccc.matchesCall(call) ) } - predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } + predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } } bindingset[conf, result] @@ -2172,7 +2164,7 @@ private predicate parameterFlow( ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, Configuration config ) { - Stage4::flow(p, true, TAccessPathApproxSome(apa0), apa, config) and + Stage4::revFlow(p, true, TAccessPathApproxSome(apa0), apa, config) and c = p.getEnclosingCallable() } @@ -2180,16 +2172,16 @@ private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, A exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | parameterFlow(p, apa, apa0, c, config) and c = ret.getEnclosingCallable() and - Stage4::flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - Stage4::flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) + Stage4::revFlow(ret, true, TAccessPathApproxSome(_), apa0, config) and + Stage4::fwdFlow(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) ) } private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | parameterMayFlowThrough(_, c, apa) and - Stage4::flow(n, true, _, apa0, config) and - Stage4::flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and n.getEnclosingCallable() = c ) } @@ -2240,7 +2232,7 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - Stage4::flowConsCand(tc, + Stage4::revFlowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) @@ -2248,7 +2240,7 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { result = - strictcount(Node n | Stage4::flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2268,7 +2260,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - Stage4::flowConsCand(head, result, config) + Stage4::revFlowConsCand(head, result, config) ) } @@ -2342,7 +2334,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - Stage4::flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2352,12 +2344,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - Stage4::flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - Stage4::flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2486,7 +2478,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - Stage4::flowConsCand(head1, result.getApprox(), _) and + Stage4::revFlowConsCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2517,7 +2509,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - Stage4::flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::revFlowConsCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2754,7 +2746,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pragma[nomagic] private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { Stage3::readCandFwd(node1, tc, _, node2, config) and - Stage4::flow(node2, config) + Stage4::revFlow(node2, config) } pragma[nomagic] @@ -2769,7 +2761,7 @@ private predicate pathReadStep( pragma[nomagic] private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { storeCand2(node1, tc, node2, _, config) and - Stage4::flow(node2, config) + Stage4::revFlow(node2, config) } pragma[nomagic] @@ -2812,7 +2804,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - Stage4::flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2848,7 +2840,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - Stage4::flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } From 3f25df902f4498f5922ba7815558899733061d9e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 19 Oct 2020 14:55:59 +0200 Subject: [PATCH 13/97] Dataflow: Rename some types and variables. --- .../java/dataflow/internal/DataFlowImpl.qll | 594 +++++++++--------- 1 file changed, 296 insertions(+), 298 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 664f89f469f..5cecf6de3d0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -272,6 +272,13 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + // class ApOption = UnitOption; + class Cc = boolean; + /** * Holds if `node` is reachable from a source in the configuration `config`. * @@ -731,33 +738,41 @@ private predicate flowIntoCallNodeCand1( } private module Stage2 { + class ApApprox = Stage1::Ap; + + class Ap = boolean; + + class ApOption = BooleanOption; + + class Cc = boolean; + /** * Holds if `node` is reachable from a source in the configuration `config`. * The Boolean `stored` records whether the tracked value is stored into a * field of `node`. * * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked + * argument in a call, and if so, `argAp` records whether the tracked * value was stored into a field of the argument. */ private predicate fwdFlow( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config + Node node, boolean fromArg, ApOption argAp, boolean stored, Configuration config ) { Stage1::revFlow(node, config) and config.isSource(node) and fromArg = false and - argStored = TBooleanNone() and + argAp = TBooleanNone() and stored = false or Stage1::revFlow(node, unbind(config)) and ( exists(Node mid | - fwdFlow(mid, fromArg, argStored, stored, config) and + fwdFlow(mid, fromArg, argAp, stored, config) and localFlowStepNodeCand1(mid, node, config) ) or exists(Node mid | - fwdFlow(mid, fromArg, argStored, stored, config) and + fwdFlow(mid, fromArg, argAp, stored, config) and additionalLocalFlowStepNodeCand1(mid, node, config) and stored = false ) @@ -766,27 +781,27 @@ private module Stage2 { fwdFlow(mid, _, _, stored, config) and jumpStep(mid, node, config) and fromArg = false and - argStored = TBooleanNone() + argAp = TBooleanNone() ) or exists(Node mid | fwdFlow(mid, _, _, stored, config) and additionalJumpStep(mid, node, config) and fromArg = false and - argStored = TBooleanNone() and + argAp = TBooleanNone() and stored = false ) or // store exists(Node mid | - fwdFlow(mid, fromArg, argStored, _, config) and + fwdFlow(mid, fromArg, argAp, _, config) and storeCand1(mid, _, node, config) and stored = true ) or // read exists(Content c | - fwdFlowRead(c, node, fromArg, argStored, config) and + fwdFlowRead(c, node, fromArg, argAp, config) and fwdFlowIsStored(c, stored, config) ) or @@ -794,17 +809,17 @@ private module Stage2 { fwdFlowIn(_, node, _, _, stored, config) and fromArg = true and if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + then argAp = TBooleanSome(stored) + else argAp = TBooleanNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, fromArg, argStored, stored, config) and + fwdFlowOut(call, node, fromArg, argAp, stored, config) and fromArg = false or exists(boolean argStored0 | fwdFlowOutFromArg(call, node, argStored0, stored, config) and - fwdFlowIsEntered(call, fromArg, argStored, argStored0, config) + fwdFlowIsEntered(call, fromArg, argAp, argStored0, config) ) ) ) @@ -825,21 +840,21 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowRead( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config + Content c, Node node, boolean fromArg, ApOption argAp, Configuration config ) { exists(Node mid | - fwdFlow(mid, fromArg, argStored, true, config) and + fwdFlow(mid, fromArg, argAp, true, config) and read(mid, c, node, config) ) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, + DataFlowCall call, ParameterNode p, boolean fromArg, ApOption argAp, boolean stored, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, fromArg, argStored, stored, config) and + fwdFlow(arg, fromArg, argAp, stored, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | stored = false or allowsFieldFlow = true @@ -848,11 +863,11 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, + DataFlowCall call, Node out, boolean fromArg, ApOption argAp, boolean stored, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, fromArg, argStored, stored, config) and + fwdFlow(ret, fromArg, argAp, stored, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | stored = false or allowsFieldFlow = true @@ -861,9 +876,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config + DataFlowCall call, Node out, boolean argAp, boolean stored, Configuration config ) { - fwdFlowOut(call, out, true, TBooleanSome(argStored), stored, config) + fwdFlowOut(call, out, true, TBooleanSome(argAp), stored, config) } /** @@ -871,11 +886,10 @@ private module Stage2 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config + DataFlowCall call, boolean fromArg, ApOption argAp, boolean stored, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, fromArg, argStored, stored, config) and + fwdFlowIn(call, p, fromArg, argAp, stored, config) and parameterThroughFlowNodeCand1(p, config) ) } @@ -886,28 +900,28 @@ private module Stage2 { * value must be read from a field of `node` in order to reach a sink. * * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` + * the enclosing callable in order to reach a sink, and if so, `returnAp` * records whether a field must be read from the returned value. */ predicate revFlow( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config + Node node, boolean toReturn, ApOption returnAp, boolean read, Configuration config ) { fwdFlow(node, _, _, false, config) and config.isSink(node) and toReturn = false and - returnRead = TBooleanNone() and + returnAp = TBooleanNone() and read = false or fwdFlow(node, _, _, unbindBool(read), unbind(config)) and ( exists(Node mid | localFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnRead, read, config) + revFlow(mid, toReturn, returnAp, read, config) ) or exists(Node mid | additionalLocalFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnRead, read, config) and + revFlow(mid, toReturn, returnAp, read, config) and read = false ) or @@ -915,20 +929,20 @@ private module Stage2 { jumpStep(node, mid, config) and revFlow(mid, _, _, read, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = TBooleanNone() ) or exists(Node mid | additionalJumpStep(node, mid, config) and revFlow(mid, _, _, read, config) and toReturn = false and - returnRead = TBooleanNone() and + returnAp = TBooleanNone() and read = false ) or // store exists(Content c | - revFlowStore(c, node, toReturn, returnRead, read, config) and + revFlowStore(c, node, toReturn, returnAp, read, config) and revFlowIsRead(c, read, config) ) or @@ -936,18 +950,18 @@ private module Stage2 { exists(Node mid, Content c, boolean read0 | read(node, c, mid, config) and fwdFlowIsStored(c, unbindBool(read0), unbind(config)) and - revFlow(mid, toReturn, returnRead, read0, config) and + revFlow(mid, toReturn, returnAp, read0, config) and read = true ) or // flow into a callable exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, read, config) and toReturn = false or - exists(boolean returnRead0 | - revFlowInToReturn(call, node, returnRead0, read, config) and - revFlowIsReturned(call, toReturn, returnRead, returnRead0, config) + exists(boolean returnAp0 | + revFlowInToReturn(call, node, returnAp0, read, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or @@ -955,8 +969,8 @@ private module Stage2 { revFlowOut(_, node, _, _, read, config) and toReturn = true and if fwdFlow(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() + then returnAp = TBooleanSome(read) + else returnAp = TBooleanNone() ) } @@ -976,12 +990,12 @@ private module Stage2 { pragma[nomagic] private predicate revFlowStore( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, + Content c, Node node, boolean toReturn, ApOption returnAp, boolean stored, Configuration config ) { exists(Node mid | storeCand1(node, c, mid, config) and - revFlow(mid, toReturn, returnRead, true, config) and + revFlow(mid, toReturn, returnAp, true, config) and fwdFlow(node, _, _, stored, unbind(config)) ) } @@ -1011,11 +1025,11 @@ private module Stage2 { pragma[nomagic] private predicate revFlowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, boolean read, Configuration config ) { exists(Node out, boolean allowsFieldFlow | - revFlow(out, toReturn, returnRead, read, config) and + revFlow(out, toReturn, returnAp, read, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | read = false or allowsFieldFlow = true @@ -1024,11 +1038,11 @@ private module Stage2 { pragma[nomagic] private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, boolean read, Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnRead, read, config) and + revFlow(p, toReturn, returnAp, read, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | read = false or allowsFieldFlow = true @@ -1037,9 +1051,9 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config + DataFlowCall call, ArgumentNode arg, boolean returnAp, boolean read, Configuration config ) { - revFlowIn(call, arg, true, TBooleanSome(returnRead), read, config) + revFlowIn(call, arg, true, TBooleanSome(returnAp), read, config) } /** @@ -1047,11 +1061,10 @@ private module Stage2 { */ pragma[nomagic] private predicate revFlowIsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config + DataFlowCall call, boolean toReturn, ApOption returnAp, boolean read, Configuration config ) { exists(ReturnNodeExt ret | - revFlowOut(call, ret, toReturn, returnRead, read, config) and + revFlowOut(call, ret, toReturn, returnAp, read, config) and fwdFlow(ret, true, TBooleanSome(_), read, config) ) } @@ -1216,51 +1229,53 @@ private predicate storeCand2( } private module Stage3 { + class ApApprox = Stage2::Ap; + + class Ap = AccessPathFront; + + class ApOption = AccessPathFrontOption; + + class Cc = boolean; + /** - * Holds if `node` is reachable with access path front `apf` from a + * Holds if `node` is reachable with access path front `ap` from a * source in the configuration `config`. * * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the + * argument in a call, and if so, `argAp` records the front of the * access path of that argument. */ pragma[nomagic] - predicate fwdFlow( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config - ) { - fwdFlow0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() + predicate fwdFlow(Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, fromArg, argAp, ap, config) and + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() } pragma[nomagic] - private predicate fwdFlow0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config - ) { + private predicate fwdFlow0(Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config) { Stage2::revFlow(node, _, _, false, config) and config.isSource(node) and fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) + argAp = TAccessPathFrontNone() and + ap = TFrontNil(getNodeType(node)) or exists(Node mid | - fwdFlow(mid, fromArg, argApf, apf, config) and + fwdFlow(mid, fromArg, argAp, ap, config) and localFlowBigStep(mid, node, true, _, config, _) ) or exists(Node mid, AccessPathFrontNil nil | - fwdFlow(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) + fwdFlow(mid, fromArg, argAp, nil, config) and + localFlowBigStep(mid, node, false, ap, config, _) ) or exists(Node mid | - fwdFlow(mid, _, _, apf, config) and + fwdFlow(mid, _, _, ap, config) and Stage2::revFlow(node, unbind(config)) and jumpStep(mid, node, config) and fromArg = false and - argApf = TAccessPathFrontNone() + argAp = TAccessPathFrontNone() ) or exists(Node mid, AccessPathFrontNil nil | @@ -1268,103 +1283,101 @@ private module Stage3 { Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) + argAp = TAccessPathFrontNone() and + ap = TFrontNil(getNodeType(node)) ) or // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - fwdFlow(mid, fromArg, argApf, apf0, config) and + exists(Node mid, TypedContent tc, Ap ap0, DataFlowType contentType | + fwdFlow(mid, fromArg, argAp, ap0, config) and storeCand2(mid, tc, node, contentType, config) and Stage2::revFlow(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) + ap.headUsesContent(tc) and + compatibleTypes(ap0.getType(), contentType) ) or // read exists(TypedContent tc | - fwdFlowRead(tc, node, fromArg, argApf, config) and - fwdFlowConsCand(tc, apf, config) and - Stage2::revFlow(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) + fwdFlowRead(tc, node, fromArg, argAp, config) and + fwdFlowConsCand(tc, ap, config) and + Stage2::revFlow(node, _, _, unbindBool(ap.toBoolNonEmpty()), unbind(config)) ) or // flow into a callable - fwdFlowIn(_, node, _, _, apf, config) and + fwdFlowIn(_, node, _, _, ap, config) and fromArg = true and - if Stage2::revFlow(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() + if Stage2::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) + then argAp = TAccessPathFrontSome(ap) + else argAp = TAccessPathFrontNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, fromArg, argApf, apf, config) and + fwdFlowOut(call, node, fromArg, argAp, ap, config) and fromArg = false or - exists(AccessPathFront argApf0 | - fwdFlowOutFromArg(call, node, argApf0, apf, config) and - fwdFlowIsEntered(call, fromArg, argApf, argApf0, config) + exists(Ap argApf0 | + fwdFlowOutFromArg(call, node, argApf0, ap, config) and + fwdFlowIsEntered(call, fromArg, argAp, argApf0, config) ) ) } pragma[nomagic] - private predicate fwdFlowConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { + private predicate fwdFlowConsCand(TypedContent tc, Ap ap, Configuration config) { exists(Node mid, Node n, DataFlowType contentType | - fwdFlow(mid, _, _, apf, config) and + fwdFlow(mid, _, _, ap, config) and storeCand2(mid, tc, n, contentType, config) and Stage2::revFlow(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) + compatibleTypes(ap.getType(), contentType) ) } pragma[nomagic] private predicate fwdFlowRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, - AccessPathFrontOption argApf, AccessPathFrontHead apf, Configuration config + Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, ApOption argAp, + AccessPathFrontHead ap, Configuration config ) { - fwdFlow(node1, fromArg, argApf, apf, config) and + fwdFlow(node1, fromArg, argAp, ap, config) and readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) + ap.headUsesContent(tc) } pragma[nomagic] private predicate fwdFlowRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config + TypedContent tc, Node node, boolean fromArg, ApOption argAp, Configuration config ) { - fwdFlowRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) + fwdFlowRead0(_, tc, tc.getContent(), node, fromArg, argAp, _, config) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config + DataFlowCall call, ParameterNode p, boolean fromArg, ApOption argAp, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, fromArg, argApf, apf, config) and + fwdFlow(arg, fromArg, argAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof AccessPathFrontNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config + DataFlowCall call, Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, fromArg, argApf, apf, config) and + fwdFlow(ret, fromArg, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof AccessPathFrontNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) + fwdFlowOut(call, node, true, TAccessPathFrontSome(argAp), ap, config) } /** @@ -1372,170 +1385,161 @@ private module Stage3 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config + DataFlowCall call, boolean fromArg, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, fromArg, argApf, apf, config) and - Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) + fwdFlowIn(call, p, fromArg, argAp, ap, config) and + Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) ) } /** - * Holds if `node` with access path front `apf` is part of a path from a + * Holds if `node` with access path front `ap` 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, and if so, `returnApf` + * the enclosing callable in order to reach a sink, and if so, `returnAp` * records the front of the access path of the returned value. */ pragma[nomagic] - predicate revFlow( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config - ) { - revFlow0(node, toReturn, returnApf, apf, config) and - fwdFlow(node, _, _, apf, config) + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) } pragma[nomagic] private predicate revFlow0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - fwdFlow(node, _, _, apf, config) and + fwdFlow(node, _, _, ap, config) and config.isSink(node) and toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil + returnAp = TAccessPathFrontNone() and + ap instanceof AccessPathFrontNil or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and - revFlow(mid, toReturn, returnApf, apf, config) + revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid, AccessPathFrontNil nil | - fwdFlow(node, _, _, apf, config) and + fwdFlow(node, _, _, ap, config) and localFlowBigStep(node, mid, false, _, config, _) and - revFlow(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof AccessPathFrontNil ) or exists(Node mid | jumpStep(node, mid, config) and - revFlow(mid, _, _, apf, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnApf = TAccessPathFrontNone() + returnAp = TAccessPathFrontNone() ) or exists(Node mid, AccessPathFrontNil nil | - fwdFlow(node, _, _, apf, config) and + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil + returnAp = TAccessPathFrontNone() and + ap instanceof AccessPathFrontNil ) or // store exists(TypedContent tc | - revFlowStore(node, tc, apf, toReturn, returnApf, config) and - revFlowConsCand(tc, apf, config) + revFlowStore(node, tc, ap, toReturn, returnAp, config) and + revFlowConsCand(tc, ap, config) ) or // read - exists(TypedContent tc, AccessPathFront apf0 | - revFlowRead(node, tc, apf, toReturn, returnApf, apf0, config) and - fwdFlowConsCand(tc, apf0, config) + exists(TypedContent tc, Ap ap0 | + revFlowRead(node, tc, ap, toReturn, returnAp, ap0, config) and + fwdFlowConsCand(tc, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnApf, apf, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(AccessPathFront returnApf0 | - revFlowInToReturn(call, node, returnApf0, apf, config) and - revFlowIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - revFlowOut(_, node, _, _, apf, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() + if fwdFlow(node, true, _, ap, config) + then returnAp = TAccessPathFrontSome(ap) + else returnAp = TAccessPathFrontNone() } pragma[nomagic] - predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config - ) { - fwdFlowRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) + predicate readCandFwd(Node node1, TypedContent tc, Ap ap, Node node2, Configuration config) { + fwdFlowRead0(node1, tc, tc.getContent(), node2, _, _, ap, config) } pragma[nomagic] private predicate revFlowRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config + Node node, TypedContent tc, Ap ap, boolean toReturn, ApOption returnAp, Ap apf0, + Configuration config ) { exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - revFlow(mid, toReturn, returnApf, apf0, config) + readCandFwd(node, tc, ap, mid, config) and + revFlow(mid, toReturn, returnAp, apf0, config) ) } pragma[nomagic] private predicate revFlowStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config + Node node, TypedContent tc, Ap ap, boolean toReturn, ApOption returnAp, Configuration config ) { exists(Node mid | - fwdFlow(node, _, _, apf, config) and + fwdFlow(node, _, _, ap, config) and storeCand2(node, tc, mid, _, unbind(config)) and - revFlow(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) + revFlow(mid, toReturn, returnAp, TFrontHead(tc), unbind(config)) ) } pragma[nomagic] - predicate revFlowConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - fwdFlowConsCand(tc, apf, config) and - revFlowRead(_, tc, _, _, _, apf, config) + predicate revFlowConsCand(TypedContent tc, Ap ap, Configuration config) { + fwdFlowConsCand(tc, ap, config) and + revFlowRead(_, tc, _, _, _, ap, config) } pragma[nomagic] private predicate revFlowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config ) { exists(Node out, boolean allowsFieldFlow | - revFlow(out, toReturn, returnApf, apf, config) and + revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof AccessPathFrontNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnApf, apf, config) and + revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof AccessPathFrontNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, config) + revFlowIn(call, arg, true, TAccessPathFrontSome(returnAp), ap, config) } /** @@ -1543,12 +1547,11 @@ private module Stage3 { */ pragma[nomagic] private predicate revFlowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret | - revFlowOut(call, ret, toReturn, returnApf, apf, config) and - fwdFlow(ret, true, TAccessPathFrontSome(_), apf, config) + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, true, TAccessPathFrontSome(_), ap, config) ) } } @@ -1757,109 +1760,115 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } private module Stage4 { + class ApApprox = Stage3::Ap; + + class Ap = AccessPathApprox; + + class ApOption = AccessPathApproxOption; + + class Cc = CallContext; + /** - * Holds if `node` is reachable with approximate access path `apa` from a source + * Holds if `node` is reachable with approximate 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, `argApa` records the approximate access path + * argument in a call, and if so, `argAp` records the approximate access path * of that argument. */ predicate fwdFlow( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config + Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { - fwdFlow0(node, cc, argApa, apf, apa, config) and + fwdFlow0(node, cc, argAp, apf, ap, config) and Stage3::revFlow(node, _, _, apf, config) } private predicate fwdFlow0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config + Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() + argAp = TAccessPathApproxNone() and + ap = TNil(getNodeType(node)) and + apf = ap.(AccessPathApproxNil).getFront() or Stage3::revFlow(node, _, _, _, unbind(config)) and ( exists(Node mid, LocalCallContext localCC | - fwdFlowLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and + fwdFlowLocalEntry(mid, cc, argAp, apf, ap, localCC, config) and localFlowBigStep(mid, node, true, _, config, localCC) ) or exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - fwdFlowLocalEntry(mid, cc, argApa, _, nil, localCC, config) and + fwdFlowLocalEntry(mid, cc, argAp, _, nil, localCC, config) and localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + apf = ap.(AccessPathApproxNil).getFront() ) or exists(Node mid | - fwdFlow(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, apf, ap, config) and jumpStep(mid, node, config) and cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + argAp = TAccessPathApproxNone() ) or exists(Node mid, AccessPathApproxNil nil | fwdFlow(mid, _, _, _, nil, config) and additionalJumpStep(mid, node, config) and cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() + argAp = TAccessPathApproxNone() and + ap = TNil(getNodeType(node)) and + apf = ap.(AccessPathApproxNil).getFront() ) ) or // store - exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) + exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, ap), apf, cc, argAp, config)) or // read exists(TypedContent tc | - fwdFlowRead(node, _, push(tc, apa), apf, cc, argApa, config) and - fwdFlowConsCand(tc, apf, apa, config) + fwdFlowRead(node, _, push(tc, ap), apf, cc, argAp, config) and + fwdFlowConsCand(tc, apf, ap, config) ) or // flow into a callable - fwdFlowIn(_, node, _, cc, _, apf, apa, config) and + fwdFlowIn(_, node, _, cc, _, apf, ap, config) and if Stage3::revFlow(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() + then argAp = TAccessPathApproxSome(ap) + else argAp = TAccessPathApproxNone() or // flow out of a callable exists(DataFlowCall call | exists(DataFlowCallable c | - fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and + fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argAp, apf, ap, config) and if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) or - exists(AccessPathApprox argApa0 | - fwdFlowOutFromArg(call, node, argApa0, apf, apa, config) and - fwdFlowIsEntered(call, cc, argApa, argApa0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, apf, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) } pragma[nomagic] private predicate fwdFlowLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config + Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, LocalCallContext localCC, + Configuration config ) { - fwdFlow(node, cc, argApa, apf, apa, config) and + fwdFlow(node, cc, argAp, apf, ap, config) and localFlowEntry(node, config) and localCC = getLocalCallContext(cc, node.getEnclosingCallable()) } pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config + Node node, TypedContent tc, Ap ap0, AccessPathFront apf, CallContext cc, ApOption argAp, + Configuration config ) { exists(Node mid, AccessPathFront apf0 | - fwdFlow(mid, cc, argApa, apf0, apa0, config) and + fwdFlow(mid, cc, argAp, apf0, ap0, config) and fwdFlowStore0(mid, tc, node, apf0, apf, config) ) } @@ -1886,20 +1895,20 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config + Node node1, TypedContent tc, AccessPathFrontHead apf0, Ap ap0, Node node2, CallContext cc, + ApOption argAp, Configuration config ) { - fwdFlow(node1, cc, argApa, apf0, apa0, config) and + fwdFlow(node1, cc, argAp, apf0, ap0, config) and Stage3::readCandFwd(node1, tc, apf0, node2, config) } pragma[nomagic] private predicate fwdFlowRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config + Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, CallContext cc, + ApOption argAp, Configuration config ) { exists(Node mid, TypedContent tc | - fwdFlowRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and + fwdFlowRead0(mid, tc, apf0, ap0, node, cc, argAp, config) and Stage3::revFlow(node, _, _, apf, unbind(config)) and Stage3::revFlowConsCand(tc, apf, unbind(config)) ) @@ -1907,21 +1916,21 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config + TypedContent tc, AccessPathFront apf, Ap ap, Configuration config ) { exists(Node n | - fwdFlow(n, _, _, apf, apa, config) and + fwdFlow(n, _, _, apf, ap, config) and fwdFlowStore0(n, tc, _, apf, _, config) ) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config + DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, ApOption argAp, + AccessPathFront apf, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - fwdFlow(arg, outercc, argApa, apf, apa, config) and + fwdFlow(arg, outercc, argAp, apf, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and c = p.getEnclosingCallable() and c = resolveCall(call, outercc) and @@ -1930,17 +1939,17 @@ private module Stage4 { then innercc = TSpecificCall(call) else innercc = TSomeCall() | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof AccessPathApproxNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config + DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, ApOption argAp, + AccessPathFront apf, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, innercc, argApa, apf, apa, config) and + fwdFlow(ret, innercc, argAp, apf, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and innerc = ret.getEnclosingCallable() and Stage3::revFlow(node, _, _, _, unbind(config)) and @@ -1950,16 +1959,15 @@ private module Stage4 { innercc.(CallContextCall).matchesCall(call) ) | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof AccessPathApproxNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config + DataFlowCall call, Node node, Ap argAp, AccessPathFront apf, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, + fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argAp), apf, ap, config) } @@ -1968,174 +1976,165 @@ private module Stage4 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config + DataFlowCall call, CallContext cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p, AccessPathFront apf | - fwdFlowIn(call, p, cc, _, argApa, apf, apa, config) and + fwdFlowIn(call, p, cc, _, argAp, apf, ap, config) and Stage3::revFlow(p, true, TAccessPathFrontSome(_), apf, config) ) } /** - * Holds if `node` with approximate access path `apa` is part of a path from a + * Holds if `node` with approximate access path `ap` 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, and if so, `returnApa` + * the enclosing callable in order to reach a sink, and if so, `returnAp` * records the approximate access path of the returned value. */ - predicate revFlow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config - ) { - revFlow0(node, toReturn, returnApa, apa, config) and - fwdFlow(node, _, _, _, apa, config) + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, _, ap, config) } private predicate revFlow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - fwdFlow(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, ap, config) and config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil + returnAp = TAccessPathApproxNone() and + ap instanceof AccessPathApproxNil or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and - revFlow(mid, toReturn, returnApa, apa, config) + revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid, AccessPathApproxNil nil | - fwdFlow(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, ap, config) and localFlowBigStep(node, mid, false, _, config, _) and - revFlow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof AccessPathApproxNil ) or exists(Node mid | jumpStep(node, mid, config) and - revFlow(mid, _, _, apa, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnApa = TAccessPathApproxNone() + returnAp = TAccessPathApproxNone() ) or exists(Node mid, AccessPathApproxNil nil | - fwdFlow(node, _, _, _, apa, config) and + fwdFlow(node, _, _, _, ap, config) and additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil + returnAp = TAccessPathApproxNone() and + ap instanceof AccessPathApproxNil ) or // store exists(TypedContent tc | - revFlowStore(tc, node, toReturn, returnApa, apa, config) and - revFlowConsCand(tc, apa, config) + revFlowStore(tc, node, toReturn, returnAp, ap, config) and + revFlowConsCand(tc, ap, config) ) or // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - revFlow(mid, toReturn, returnApa, apa0, config) + exists(Node mid, Ap ap0 | + readFlowFwd(node, _, mid, ap, ap0, config) and + revFlow(mid, toReturn, returnAp, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnApa, apa, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(AccessPathApprox returnApa0 | - revFlowInToReturn(call, node, returnApa0, apa, config) and - revFlowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Ap returnApa0 | + revFlowInToReturn(call, node, returnApa0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnApa0, config) ) ) or // flow out of a callable - revFlowOut(_, node, _, _, apa, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() + if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, ap, config) + then returnAp = TAccessPathApproxSome(ap) + else returnAp = TAccessPathApproxNone() } pragma[nomagic] private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config + Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config ) { storeCand2(node1, tc, node2, _, config) and - fwdFlowStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) + fwdFlowStore(node2, tc, ap, _, _, _, config) and + ap0 = push(tc, ap) } pragma[nomagic] private predicate revFlowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config + TypedContent tc, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - revFlow(mid, toReturn, returnApa, apa0, config) + exists(Node mid, Ap ap0 | + storeFlowFwd(node, tc, mid, ap, ap0, config) and + revFlow(mid, toReturn, returnAp, ap0, config) ) } pragma[nomagic] private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config + Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config ) { exists(AccessPathFrontHead apf | Stage3::readCandFwd(node1, tc, apf, node2, config) and - fwdFlowRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - fwdFlowConsCand(tc, _, apa0, unbind(config)) + fwdFlowRead(node2, apf, ap, _, _, _, config) and + ap0 = pop(tc, ap) and + fwdFlowConsCand(tc, _, ap0, unbind(config)) ) } pragma[nomagic] - predicate revFlowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { + predicate revFlowConsCand(TypedContent tc, Ap ap, Configuration config) { exists(Node n, Node mid | - revFlow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) + revFlow(mid, _, _, ap, config) and + readFlowFwd(n, tc, mid, _, ap, config) ) } pragma[nomagic] private predicate revFlowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config ) { exists(Node out, boolean allowsFieldFlow | - revFlow(out, toReturn, returnApa, apa, config) and + revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof AccessPathApproxNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnApa, apa, config) and + revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof AccessPathApproxNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) + revFlowIn(call, arg, true, TAccessPathApproxSome(returnAp), ap, config) } /** @@ -2143,12 +2142,11 @@ private module Stage4 { */ pragma[nomagic] private predicate revFlowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, CallContextCall ccc | - revFlowOut(call, ret, toReturn, returnApa, apa, config) and - fwdFlow(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, TAccessPathApproxSome(_), _, ap, config) and ccc.matchesCall(call) ) } From 586d52fac095ada03360d75fa82bb0f167ebf75e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 19 Oct 2020 15:39:45 +0200 Subject: [PATCH 14/97] Dataflow: More renaming. --- .../java/dataflow/internal/DataFlowImpl.qll | 268 +++++++++--------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 5cecf6de3d0..46d73746a69 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -276,54 +276,55 @@ private module Stage1 { class Ap = Unit; - // class ApOption = UnitOption; + class ApOption = Unit; + class Cc = boolean; /** * Holds if `node` is reachable from a source in the configuration `config`. * - * The Boolean `fromArg` records whether the node is reached through an + * The Boolean `cc` records whether the node is reached through an * argument in a call. */ - predicate fwdFlow(Node node, boolean fromArg, Configuration config) { + predicate fwdFlow(Node node, Cc cc, Configuration config) { not fullBarrier(node, config) and ( config.isSource(node) and - fromArg = false + cc = false or exists(Node mid | - fwdFlow(mid, fromArg, config) and + fwdFlow(mid, cc, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - fwdFlow(mid, fromArg, config) and + fwdFlow(mid, cc, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | fwdFlow(mid, config) and jumpStep(mid, node, config) and - fromArg = false + cc = false ) or exists(Node mid | fwdFlow(mid, config) and additionalJumpStep(mid, node, config) and - fromArg = false + cc = false ) or // store exists(Node mid | useFieldFlow(config) and - fwdFlow(mid, fromArg, config) and + fwdFlow(mid, cc, config) and store(mid, _, node, _) and not outBarrier(mid, config) ) or // read exists(Content c | - fwdFlowRead(c, node, fromArg, config) and + fwdFlowRead(c, node, cc, config) and fwdFlowIsStored(c, config) and not inBarrier(node, config) ) @@ -332,16 +333,16 @@ private module Stage1 { exists(Node arg | fwdFlow(arg, config) and viableParamArg(_, node, arg) and - fromArg = true + cc = true ) or // flow out of a callable exists(DataFlowCall call | fwdFlowOut(call, node, false, config) and - fromArg = false + cc = false or fwdFlowOutFromArg(call, node, config) and - fwdFlowIsEntered(call, fromArg, config) + fwdFlowIsEntered(call, cc, config) ) ) } @@ -349,9 +350,9 @@ private module Stage1 { private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } pragma[nomagic] - private predicate fwdFlowRead(Content c, Node node, boolean fromArg, Configuration config) { + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { exists(Node mid | - fwdFlow(mid, fromArg, config) and + fwdFlow(mid, cc, config) and read(mid, c, node) ) } @@ -371,17 +372,17 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, boolean fromArg, Configuration config) { + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { exists(ReturnNodeExt ret | - fwdFlow(ret, fromArg, config) and + fwdFlow(ret, cc, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] - private predicate fwdFlowOut(DataFlowCall call, Node out, boolean fromArg, Configuration config) { + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, fromArg, config) and + fwdFlowReturnPosition(pos, cc, config) and viableReturnPosOut(call, pos, out) ) } @@ -395,9 +396,9 @@ private module Stage1 { * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate fwdFlowIsEntered(DataFlowCall call, boolean fromArg, Configuration config) { + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { exists(ArgumentNode arg | - fwdFlow(arg, fromArg, config) and + fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) } @@ -748,78 +749,78 @@ private module Stage2 { /** * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a + * The Boolean `ap` records whether the tracked value is stored into a * field of `node`. * - * The Boolean `fromArg` records whether the node is reached through an + * The Boolean `cc` records whether the node is reached through an * argument in a call, and if so, `argAp` records whether the tracked * value was stored into a field of the argument. */ private predicate fwdFlow( - Node node, boolean fromArg, ApOption argAp, boolean stored, Configuration config + Node node, Cc cc, ApOption argAp, Ap ap, Configuration config ) { Stage1::revFlow(node, config) and config.isSource(node) and - fromArg = false and + cc = false and argAp = TBooleanNone() and - stored = false + ap = false or Stage1::revFlow(node, unbind(config)) and ( exists(Node mid | - fwdFlow(mid, fromArg, argAp, stored, config) and + fwdFlow(mid, cc, argAp, ap, config) and localFlowStepNodeCand1(mid, node, config) ) or exists(Node mid | - fwdFlow(mid, fromArg, argAp, stored, config) and + fwdFlow(mid, cc, argAp, ap, config) and additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false + ap = false ) or exists(Node mid | - fwdFlow(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and - fromArg = false and + cc = false and argAp = TBooleanNone() ) or exists(Node mid | - fwdFlow(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and additionalJumpStep(mid, node, config) and - fromArg = false and + cc = false and argAp = TBooleanNone() and - stored = false + ap = false ) or // store exists(Node mid | - fwdFlow(mid, fromArg, argAp, _, config) and + fwdFlow(mid, cc, argAp, _, config) and storeCand1(mid, _, node, config) and - stored = true + ap = true ) or // read exists(Content c | - fwdFlowRead(c, node, fromArg, argAp, config) and - fwdFlowIsStored(c, stored, config) + fwdFlowRead(c, node, cc, argAp, config) and + fwdFlowIsStored(c, ap, config) ) or // flow into a callable - fwdFlowIn(_, node, _, _, stored, config) and - fromArg = true and + fwdFlowIn(_, node, _, _, ap, config) and + cc = true and if parameterThroughFlowNodeCand1(node, config) - then argAp = TBooleanSome(stored) + then argAp = TBooleanSome(ap) else argAp = TBooleanNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, fromArg, argAp, stored, config) and - fromArg = false + fwdFlowOut(call, node, cc, argAp, ap, config) and + cc = false or exists(boolean argStored0 | - fwdFlowOutFromArg(call, node, argStored0, stored, config) and - fwdFlowIsEntered(call, fromArg, argAp, argStored0, config) + fwdFlowOutFromArg(call, node, argStored0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argStored0, config) ) ) ) @@ -829,56 +830,56 @@ private module Stage2 { * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ pragma[noinline] - private predicate fwdFlowIsStored(Content c, boolean stored, Configuration config) { + private predicate fwdFlowIsStored(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and Stage1::revFlow(node, unbind(config)) and - fwdFlow(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and storeCand1(mid, c, node, config) ) } pragma[nomagic] private predicate fwdFlowRead( - Content c, Node node, boolean fromArg, ApOption argAp, Configuration config + Content c, Node node, Cc cc, ApOption argAp, Configuration config ) { exists(Node mid | - fwdFlow(mid, fromArg, argAp, true, config) and + fwdFlow(mid, cc, argAp, true, config) and read(mid, c, node, config) ) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, boolean fromArg, ApOption argAp, boolean stored, + DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, fromArg, argAp, stored, config) and + fwdFlow(arg, cc, argAp, ap, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | - stored = false or allowsFieldFlow = true + ap = false or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node out, boolean fromArg, ApOption argAp, boolean stored, + DataFlowCall call, Node out, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, fromArg, argAp, stored, config) and + fwdFlow(ret, cc, argAp, ap, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | - stored = false or allowsFieldFlow = true + ap = false or allowsFieldFlow = true ) } pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node out, boolean argAp, boolean stored, Configuration config + DataFlowCall call, Node out, boolean argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, true, TBooleanSome(argAp), stored, config) + fwdFlowOut(call, out, true, TBooleanSome(argAp), ap, config) } /** @@ -886,17 +887,17 @@ private module Stage2 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, boolean fromArg, ApOption argAp, boolean stored, Configuration config + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, fromArg, argAp, stored, config) and + fwdFlowIn(call, p, cc, argAp, ap, config) and parameterThroughFlowNodeCand1(p, config) ) } /** * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked + * configuration `config`. The Boolean `ap` records whether the tracked * value must be read from a field of `node` in order to reach a sink. * * The Boolean `toReturn` records whether the node must be returned from @@ -904,72 +905,72 @@ private module Stage2 { * records whether a field must be read from the returned value. */ predicate revFlow( - Node node, boolean toReturn, ApOption returnAp, boolean read, Configuration config + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { fwdFlow(node, _, _, false, config) and config.isSink(node) and toReturn = false and returnAp = TBooleanNone() and - read = false + ap = false or - fwdFlow(node, _, _, unbindBool(read), unbind(config)) and + fwdFlow(node, _, _, unbindBool(ap), unbind(config)) and ( exists(Node mid | localFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnAp, read, config) + revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid | additionalLocalFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnAp, read, config) and - read = false + revFlow(mid, toReturn, returnAp, ap, config) and + ap = false ) or exists(Node mid | jumpStep(node, mid, config) and - revFlow(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and returnAp = TBooleanNone() ) or exists(Node mid | additionalJumpStep(node, mid, config) and - revFlow(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and returnAp = TBooleanNone() and - read = false + ap = false ) or // store exists(Content c | - revFlowStore(c, node, toReturn, returnAp, read, config) and - revFlowIsRead(c, read, config) + revFlowStore(c, node, toReturn, returnAp, ap, config) and + revFlowIsRead(c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | + exists(Node mid, Content c, Ap ap0 | read(node, c, mid, config) and - fwdFlowIsStored(c, unbindBool(read0), unbind(config)) and - revFlow(mid, toReturn, returnAp, read0, config) and - read = true + fwdFlowIsStored(c, unbindBool(ap0), unbind(config)) and + revFlow(mid, toReturn, returnAp, ap0, config) and + ap = true ) or // flow into a callable exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or exists(boolean returnAp0 | - revFlowInToReturn(call, node, returnAp0, read, config) and + revFlowInToReturn(call, node, returnAp0, ap, config) and revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - revFlowOut(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, TBooleanSome(_), unbindBool(read), config) - then returnAp = TBooleanSome(read) + if fwdFlow(node, true, TBooleanSome(_), unbindBool(ap), config) + then returnAp = TBooleanSome(ap) else returnAp = TBooleanNone() ) } @@ -978,25 +979,24 @@ private module Stage2 { * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[noinline] - private predicate revFlowIsRead(Content c, boolean read, Configuration config) { + private predicate revFlowIsRead(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and fwdFlow(node, _, _, true, unbind(config)) and read(node, c, mid, config) and - fwdFlowIsStored(c, unbindBool(read), unbind(config)) and - revFlow(mid, _, _, read, config) + fwdFlowIsStored(c, unbindBool(ap), unbind(config)) and + revFlow(mid, _, _, ap, config) ) } pragma[nomagic] private predicate revFlowStore( - Content c, Node node, boolean toReturn, ApOption returnAp, boolean stored, - Configuration config + Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(Node mid | storeCand1(node, c, mid, config) and revFlow(mid, toReturn, returnAp, true, config) and - fwdFlow(node, _, _, stored, unbind(config)) + fwdFlow(node, _, _, ap, unbind(config)) ) } @@ -1004,10 +1004,10 @@ private module Stage2 { * Holds if `c` is the target of a store in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowIsStored(Content c, boolean stored, Configuration conf) { + private predicate revFlowIsStored(Content c, Ap ap, Configuration conf) { exists(Node node | - revFlowStore(c, node, _, _, stored, conf) and - revFlow(node, _, _, stored, conf) + revFlowStore(c, node, _, _, ap, conf) and + revFlow(node, _, _, ap, conf) ) } @@ -1025,35 +1025,35 @@ private module Stage2 { pragma[nomagic] private predicate revFlowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, boolean read, + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(Node out, boolean allowsFieldFlow | - revFlow(out, toReturn, returnAp, read, config) and + revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | - read = false or allowsFieldFlow = true + ap = false or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, boolean read, + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, read, config) and + revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | - read = false or allowsFieldFlow = true + ap = false or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnAp, boolean read, Configuration config + DataFlowCall call, ArgumentNode arg, boolean returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TBooleanSome(returnAp), read, config) + revFlowIn(call, arg, true, TBooleanSome(returnAp), ap, config) } /** @@ -1061,11 +1061,11 @@ private module Stage2 { */ pragma[nomagic] private predicate revFlowIsReturned( - DataFlowCall call, boolean toReturn, ApOption returnAp, boolean read, Configuration config + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret | - revFlowOut(call, ret, toReturn, returnAp, read, config) and - fwdFlow(ret, true, TBooleanSome(_), read, config) + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, true, TBooleanSome(_), ap, config) ) } @@ -1241,32 +1241,32 @@ private module Stage3 { * Holds if `node` is reachable with access path front `ap` from a * source in the configuration `config`. * - * The Boolean `fromArg` records whether the node is reached through an + * The Boolean `cc` records whether the node is reached through an * argument in a call, and if so, `argAp` records the front of the * access path of that argument. */ pragma[nomagic] - predicate fwdFlow(Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config) { - fwdFlow0(node, fromArg, argAp, ap, config) and + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and not ap.isClearedAt(node) and if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() } pragma[nomagic] - private predicate fwdFlow0(Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config) { + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage2::revFlow(node, _, _, false, config) and config.isSource(node) and - fromArg = false and + cc = false and argAp = TAccessPathFrontNone() and ap = TFrontNil(getNodeType(node)) or exists(Node mid | - fwdFlow(mid, fromArg, argAp, ap, config) and + fwdFlow(mid, cc, argAp, ap, config) and localFlowBigStep(mid, node, true, _, config, _) ) or exists(Node mid, AccessPathFrontNil nil | - fwdFlow(mid, fromArg, argAp, nil, config) and + fwdFlow(mid, cc, argAp, nil, config) and localFlowBigStep(mid, node, false, ap, config, _) ) or @@ -1274,7 +1274,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and Stage2::revFlow(node, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and + cc = false and argAp = TAccessPathFrontNone() ) or @@ -1282,14 +1282,14 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and + cc = false and argAp = TAccessPathFrontNone() and ap = TFrontNil(getNodeType(node)) ) or // store exists(Node mid, TypedContent tc, Ap ap0, DataFlowType contentType | - fwdFlow(mid, fromArg, argAp, ap0, config) and + fwdFlow(mid, cc, argAp, ap0, config) and storeCand2(mid, tc, node, contentType, config) and Stage2::revFlow(node, _, _, true, unbind(config)) and ap.headUsesContent(tc) and @@ -1298,26 +1298,26 @@ private module Stage3 { or // read exists(TypedContent tc | - fwdFlowRead(tc, node, fromArg, argAp, config) and + fwdFlowRead(tc, node, cc, argAp, config) and fwdFlowConsCand(tc, ap, config) and Stage2::revFlow(node, _, _, unbindBool(ap.toBoolNonEmpty()), unbind(config)) ) or // flow into a callable fwdFlowIn(_, node, _, _, ap, config) and - fromArg = true and + cc = true and if Stage2::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) then argAp = TAccessPathFrontSome(ap) else argAp = TAccessPathFrontNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, fromArg, argAp, ap, config) and - fromArg = false + fwdFlowOut(call, node, cc, argAp, ap, config) and + cc = false or exists(Ap argApf0 | fwdFlowOutFromArg(call, node, argApf0, ap, config) and - fwdFlowIsEntered(call, fromArg, argAp, argApf0, config) + fwdFlowIsEntered(call, cc, argAp, argApf0, config) ) ) } @@ -1334,27 +1334,27 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, ApOption argAp, + Node node1, TypedContent tc, Content c, Node node2, Cc cc, ApOption argAp, AccessPathFrontHead ap, Configuration config ) { - fwdFlow(node1, fromArg, argAp, ap, config) and + fwdFlow(node1, cc, argAp, ap, config) and readCand2(node1, c, node2, config) and ap.headUsesContent(tc) } pragma[nomagic] private predicate fwdFlowRead( - TypedContent tc, Node node, boolean fromArg, ApOption argAp, Configuration config + TypedContent tc, Node node, Cc cc, ApOption argAp, Configuration config ) { - fwdFlowRead0(_, tc, tc.getContent(), node, fromArg, argAp, _, config) + fwdFlowRead0(_, tc, tc.getContent(), node, cc, argAp, _, config) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, boolean fromArg, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, fromArg, argAp, ap, config) and + fwdFlow(arg, cc, argAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | ap instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1363,10 +1363,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, boolean fromArg, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node node, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, fromArg, argAp, ap, config) and + fwdFlow(ret, cc, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) | ap instanceof AccessPathFrontNil or allowsFieldFlow = true @@ -1385,10 +1385,10 @@ private module Stage3 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, boolean fromArg, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, fromArg, argAp, ap, config) and + fwdFlowIn(call, p, cc, argAp, ap, config) and Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) ) } @@ -1777,14 +1777,14 @@ private module Stage4 { * of that argument. */ predicate fwdFlow( - Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config + Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { fwdFlow0(node, cc, argAp, apf, ap, config) and Stage3::revFlow(node, _, _, apf, config) } private predicate fwdFlow0( - Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config + Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and @@ -1854,7 +1854,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowLocalEntry( - Node node, CallContext cc, ApOption argAp, AccessPathFront apf, Ap ap, LocalCallContext localCC, + Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, LocalCallContext localCC, Configuration config ) { fwdFlow(node, cc, argAp, apf, ap, config) and @@ -1864,7 +1864,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, Ap ap0, AccessPathFront apf, CallContext cc, ApOption argAp, + Node node, TypedContent tc, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, Configuration config ) { exists(Node mid, AccessPathFront apf0 | @@ -1895,7 +1895,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, Ap ap0, Node node2, CallContext cc, + Node node1, TypedContent tc, AccessPathFrontHead apf0, Ap ap0, Node node2, Cc cc, ApOption argAp, Configuration config ) { fwdFlow(node1, cc, argAp, apf0, ap0, config) and @@ -1904,7 +1904,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowRead( - Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, CallContext cc, + Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, Configuration config ) { exists(Node mid, TypedContent tc | @@ -1926,7 +1926,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, ApOption argAp, + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | @@ -1945,7 +1945,7 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, ApOption argAp, + DataFlowCall call, Node node, Cc innercc, DataFlowCallable innerc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | @@ -1976,7 +1976,7 @@ private module Stage4 { */ pragma[nomagic] private predicate fwdFlowIsEntered( - DataFlowCall call, CallContext cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p, AccessPathFront apf | fwdFlowIn(call, p, cc, _, argAp, apf, ap, config) and From 1fe423550f41093262e3d8f50ded46661f123740 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 20 Oct 2020 13:37:30 +0200 Subject: [PATCH 15/97] Dataflow: Stage comments and some formatting. --- .../java/dataflow/internal/DataFlowImpl.qll | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 46d73746a69..d81af5c6d86 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -280,6 +280,7 @@ private module Stage1 { class Cc = boolean; + /* Begin: Stage 1 logic. */ /** * Holds if `node` is reachable from a source in the configuration `config`. * @@ -303,13 +304,13 @@ private module Stage1 { ) or exists(Node mid | - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and jumpStep(mid, node, config) and cc = false ) or exists(Node mid | - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and additionalJumpStep(mid, node, config) and cc = false ) @@ -331,7 +332,7 @@ private module Stage1 { or // flow into a callable exists(Node arg | - fwdFlow(arg, config) and + fwdFlow(arg, _, config) and viableParamArg(_, node, arg) and cc = true ) @@ -559,6 +560,7 @@ private module Stage1 { pragma[nomagic] predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + /* End: Stage 1 logic. */ } bindingset[result, b] @@ -747,6 +749,7 @@ private module Stage2 { class Cc = boolean; + /* Begin: Stage 2 logic. */ /** * Holds if `node` is reachable from a source in the configuration `config`. * The Boolean `ap` records whether the tracked value is stored into a @@ -756,9 +759,7 @@ private module Stage2 { * argument in a call, and if so, `argAp` records whether the tracked * value was stored into a field of the argument. */ - private predicate fwdFlow( - Node node, Cc cc, ApOption argAp, Ap ap, Configuration config - ) { + private predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage1::revFlow(node, config) and config.isSource(node) and cc = false and @@ -840,9 +841,7 @@ private module Stage2 { } pragma[nomagic] - private predicate fwdFlowRead( - Content c, Node node, Cc cc, ApOption argAp, Configuration config - ) { + private predicate fwdFlowRead(Content c, Node node, Cc cc, ApOption argAp, Configuration config) { exists(Node mid | fwdFlow(mid, cc, argAp, true, config) and read(mid, c, node, config) @@ -851,8 +850,7 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, - Configuration config + DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | fwdFlow(arg, cc, argAp, ap, config) and @@ -864,8 +862,7 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc cc, ApOption argAp, Ap ap, - Configuration config + DataFlowCall call, Node out, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | fwdFlow(ret, cc, argAp, ap, config) and @@ -904,9 +901,7 @@ private module Stage2 { * the enclosing callable in order to reach a sink, and if so, `returnAp` * records whether a field must be read from the returned value. */ - predicate revFlow( - Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config - ) { + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { fwdFlow(node, _, _, false, config) and config.isSink(node) and toReturn = false and @@ -1070,6 +1065,7 @@ private module Stage2 { } predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + /* End: Stage 2 logic. */ } pragma[nomagic] @@ -1237,6 +1233,7 @@ private module Stage3 { class Cc = boolean; + /* Begin: Stage 3 logic. */ /** * Holds if `node` is reachable with access path front `ap` from a * source in the configuration `config`. @@ -1554,6 +1551,7 @@ private module Stage3 { fwdFlow(ret, true, TAccessPathFrontSome(_), ap, config) ) } + /* End: Stage 3 logic. */ } /** @@ -1768,6 +1766,7 @@ private module Stage4 { class Cc = CallContext; + /* Begin: Stage 4 logic. */ /** * Holds if `node` is reachable with approximate access path `ap` from a source * in the configuration `config`. @@ -1904,8 +1903,8 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowRead( - Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, Cc cc, - ApOption argAp, Configuration config + Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, + Configuration config ) { exists(Node mid, TypedContent tc | fwdFlowRead0(mid, tc, apf0, ap0, node, cc, argAp, config) and @@ -1926,8 +1925,8 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, - AccessPathFront apf, Ap ap, Configuration config + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, AccessPathFront apf, + Ap ap, Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | fwdFlow(arg, outercc, argAp, apf, ap, config) and @@ -2152,6 +2151,7 @@ private module Stage4 { } predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } + /* End: Stage 4 logic. */ } bindingset[conf, result] From 7eeae49e06399f446161e07bbf3d68b68d822d7d Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 21 Oct 2020 15:42:53 +0200 Subject: [PATCH 16/97] Dataflow: Remove AccessPathFront column. This column is functionally determined from the access path, and was merely included to help with some join-orders that no longer appear problematic. --- .../java/dataflow/internal/DataFlowImpl.qll | 135 +++++++++--------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index d81af5c6d86..20b3f2c5204 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1775,77 +1775,74 @@ private module Stage4 { * argument in a call, and if so, `argAp` records the approximate access path * of that argument. */ - predicate fwdFlow( - Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config - ) { - fwdFlow0(node, cc, argAp, apf, ap, config) and - Stage3::revFlow(node, _, _, apf, config) + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + Stage3::revFlow(node, _, _, ap.getFront(), config) } - private predicate fwdFlow0( - Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, Configuration config - ) { + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and cc instanceof CallContextAny and argAp = TAccessPathApproxNone() and - ap = TNil(getNodeType(node)) and - apf = ap.(AccessPathApproxNil).getFront() + ap = TNil(getNodeType(node)) or Stage3::revFlow(node, _, _, _, unbind(config)) and ( exists(Node mid, LocalCallContext localCC | - fwdFlowLocalEntry(mid, cc, argAp, apf, ap, localCC, config) and + fwdFlowLocalEntry(mid, cc, argAp, ap, localCC, config) and localFlowBigStep(mid, node, true, _, config, localCC) ) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - fwdFlowLocalEntry(mid, cc, argAp, _, nil, localCC, config) and + exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC, AccessPathFront apf | + fwdFlowLocalEntry(mid, cc, argAp, nil, localCC, config) and localFlowBigStep(mid, node, false, apf, config, localCC) and apf = ap.(AccessPathApproxNil).getFront() ) or exists(Node mid | - fwdFlow(mid, _, _, apf, ap, config) and + fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and cc instanceof CallContextAny and argAp = TAccessPathApproxNone() ) or exists(Node mid, AccessPathApproxNil nil | - fwdFlow(mid, _, _, _, nil, config) and + fwdFlow(mid, _, _, nil, config) and additionalJumpStep(mid, node, config) and cc instanceof CallContextAny and argAp = TAccessPathApproxNone() and - ap = TNil(getNodeType(node)) and - apf = ap.(AccessPathApproxNil).getFront() + ap = TNil(getNodeType(node)) ) ) or // store - exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, ap), apf, cc, argAp, config)) + exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, ap), cc, argAp, config)) or // read - exists(TypedContent tc | - fwdFlowRead(node, _, push(tc, ap), apf, cc, argAp, config) and + exists(TypedContent tc, AccessPathFront apf | + fwdFlowRead(node, push(tc, ap), apf, cc, argAp, config) and fwdFlowConsCand(tc, apf, ap, config) ) or // flow into a callable - fwdFlowIn(_, node, _, cc, _, apf, ap, config) and - if Stage3::revFlow(node, true, _, apf, config) - then argAp = TAccessPathApproxSome(ap) - else argAp = TAccessPathApproxNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = ap.getFront() and + if Stage3::revFlow(node, true, _, apa, config) + then argAp = TAccessPathApproxSome(ap) + else argAp = TAccessPathApproxNone() + ) or // flow out of a callable exists(DataFlowCall call | exists(DataFlowCallable c | - fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argAp, apf, ap, config) and + fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argAp, ap, config) and if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() ) or exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, apf, ap, config) and + fwdFlowOutFromArg(call, node, argAp0, ap, config) and fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) @@ -1853,22 +1850,20 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowLocalEntry( - Node node, Cc cc, ApOption argAp, AccessPathFront apf, Ap ap, LocalCallContext localCC, - Configuration config + Node node, Cc cc, ApOption argAp, Ap ap, LocalCallContext localCC, Configuration config ) { - fwdFlow(node, cc, argAp, apf, ap, config) and + fwdFlow(node, cc, argAp, ap, config) and localFlowEntry(node, config) and localCC = getLocalCallContext(cc, node.getEnclosingCallable()) } pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, - Configuration config + Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config ) { - exists(Node mid, AccessPathFront apf0 | - fwdFlow(mid, cc, argAp, apf0, ap0, config) and - fwdFlowStore0(mid, tc, node, apf0, apf, config) + exists(Node mid | + fwdFlow(mid, cc, argAp, ap0, config) and + fwdFlowStore0(mid, tc, node, ap0.getFront(), config) ) } @@ -1884,30 +1879,29 @@ private module Stage4 { pragma[noinline] private predicate fwdFlowStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config + Node mid, TypedContent tc, Node node, AccessPathFront apf0, Configuration config ) { - storeCand(mid, tc, node, apf0, apf, config) and - Stage3::revFlowConsCand(tc, apf0, config) and - Stage3::revFlow(node, _, _, apf, unbind(config)) + exists(AccessPathFront apf | + storeCand(mid, tc, node, apf0, apf, config) and + Stage3::revFlowConsCand(tc, apf0, config) and + Stage3::revFlow(node, _, _, apf, unbind(config)) + ) } pragma[nomagic] private predicate fwdFlowRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, Ap ap0, Node node2, Cc cc, - ApOption argAp, Configuration config + Node node1, TypedContent tc, Ap ap0, Node node2, Cc cc, ApOption argAp, Configuration config ) { - fwdFlow(node1, cc, argAp, apf0, ap0, config) and - Stage3::readCandFwd(node1, tc, apf0, node2, config) + fwdFlow(node1, cc, argAp, ap0, config) and + Stage3::readCandFwd(node1, tc, ap0.getFront(), node2, config) } pragma[nomagic] private predicate fwdFlowRead( - Node node, AccessPathFrontHead apf0, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, - Configuration config + Node node, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, Configuration config ) { exists(Node mid, TypedContent tc | - fwdFlowRead0(mid, tc, apf0, ap0, node, cc, argAp, config) and + fwdFlowRead0(mid, tc, ap0, node, cc, argAp, config) and Stage3::revFlow(node, _, _, apf, unbind(config)) and Stage3::revFlowConsCand(tc, apf, unbind(config)) ) @@ -1918,18 +1912,19 @@ private module Stage4 { TypedContent tc, AccessPathFront apf, Ap ap, Configuration config ) { exists(Node n | - fwdFlow(n, _, _, apf, ap, config) and - fwdFlowStore0(n, tc, _, apf, _, config) + fwdFlow(n, _, _, ap, config) and + apf = ap.getFront() and + fwdFlowStore0(n, tc, _, apf, config) ) } pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, AccessPathFront apf, - Ap ap, Configuration config + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - fwdFlow(arg, outercc, argAp, apf, ap, config) and + fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and c = p.getEnclosingCallable() and c = resolveCall(call, outercc) and @@ -1944,11 +1939,11 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, DataFlowCallable innerc, ApOption argAp, - AccessPathFront apf, Ap ap, Configuration config + DataFlowCall call, Node node, Cc innercc, DataFlowCallable innerc, ApOption argAp, Ap ap, + Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, innercc, argAp, apf, ap, config) and + fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and innerc = ret.getEnclosingCallable() and Stage3::revFlow(node, _, _, _, unbind(config)) and @@ -1964,10 +1959,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, AccessPathFront apf, Ap ap, Configuration config + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argAp), apf, ap, - config) + fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argAp), ap, config) } /** @@ -1977,9 +1971,9 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p, AccessPathFront apf | - fwdFlowIn(call, p, cc, _, argAp, apf, ap, config) and - Stage3::revFlow(p, true, TAccessPathFrontSome(_), apf, config) + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + Stage3::revFlow(p, true, TAccessPathFrontSome(_), ap.getFront(), config) ) } @@ -1993,13 +1987,13 @@ private module Stage4 { */ predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { revFlow0(node, toReturn, returnAp, ap, config) and - fwdFlow(node, _, _, _, ap, config) + fwdFlow(node, _, _, ap, config) } private predicate revFlow0( Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - fwdFlow(node, _, _, _, ap, config) and + fwdFlow(node, _, _, ap, config) and config.isSink(node) and toReturn = false and returnAp = TAccessPathApproxNone() and @@ -2011,7 +2005,7 @@ private module Stage4 { ) or exists(Node mid, AccessPathApproxNil nil | - fwdFlow(node, _, _, _, ap, config) and + fwdFlow(node, _, _, ap, config) and localFlowBigStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and ap instanceof AccessPathApproxNil @@ -2025,7 +2019,7 @@ private module Stage4 { ) or exists(Node mid, AccessPathApproxNil nil | - fwdFlow(node, _, _, _, ap, config) and + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and @@ -2059,7 +2053,7 @@ private module Stage4 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, ap, config) + if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), ap, config) then returnAp = TAccessPathApproxSome(ap) else returnAp = TAccessPathApproxNone() } @@ -2069,7 +2063,7 @@ private module Stage4 { Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config ) { storeCand2(node1, tc, node2, _, config) and - fwdFlowStore(node2, tc, ap, _, _, _, config) and + fwdFlowStore(node2, tc, ap, _, _, config) and ap0 = push(tc, ap) } @@ -2089,7 +2083,8 @@ private module Stage4 { ) { exists(AccessPathFrontHead apf | Stage3::readCandFwd(node1, tc, apf, node2, config) and - fwdFlowRead(node2, apf, ap, _, _, _, config) and + apf = ap.getFront() and + fwdFlowRead(node2, ap, _, _, _, config) and ap0 = pop(tc, ap) and fwdFlowConsCand(tc, _, ap0, unbind(config)) ) @@ -2145,7 +2140,7 @@ private module Stage4 { ) { exists(ReturnNodeExt ret, CallContextCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, ccc, TAccessPathApproxSome(_), _, ap, config) and + fwdFlow(ret, ccc, TAccessPathApproxSome(_), ap, config) and ccc.matchesCall(call) ) } @@ -2171,7 +2166,7 @@ private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, A parameterFlow(p, apa, apa0, c, config) and c = ret.getEnclosingCallable() and Stage4::revFlow(ret, true, TAccessPathApproxSome(_), apa0, config) and - Stage4::fwdFlow(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) + Stage4::fwdFlow(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) ) } @@ -2179,7 +2174,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration exists(DataFlowCallable c, AccessPathApprox apa0 | parameterMayFlowThrough(_, c, apa) and Stage4::revFlow(n, true, _, apa0, config) and - Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } From c054295347e7c7eb53fcdb7a5a7439c7cef66cdb Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 21 Oct 2020 16:03:04 +0200 Subject: [PATCH 17/97] Dataflow: Rename option type branches. --- .../java/dataflow/internal/DataFlowImpl.qll | 94 ++++++++++--------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 20b3f2c5204..ead71f43b75 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -747,6 +747,10 @@ private module Stage2 { class ApOption = BooleanOption; + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + class Cc = boolean; /* Begin: Stage 2 logic. */ @@ -763,7 +767,7 @@ private module Stage2 { Stage1::revFlow(node, config) and config.isSource(node) and cc = false and - argAp = TBooleanNone() and + argAp = apNone() and ap = false or Stage1::revFlow(node, unbind(config)) and @@ -783,14 +787,14 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and cc = false and - argAp = TBooleanNone() + argAp = apNone() ) or exists(Node mid | fwdFlow(mid, _, _, ap, config) and additionalJumpStep(mid, node, config) and cc = false and - argAp = TBooleanNone() and + argAp = apNone() and ap = false ) or @@ -810,9 +814,7 @@ private module Stage2 { // flow into a callable fwdFlowIn(_, node, _, _, ap, config) and cc = true and - if parameterThroughFlowNodeCand1(node, config) - then argAp = TBooleanSome(ap) - else argAp = TBooleanNone() + if parameterThroughFlowNodeCand1(node, config) then argAp = apSome(ap) else argAp = apNone() or // flow out of a callable exists(DataFlowCall call | @@ -876,7 +878,7 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, boolean argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, true, TBooleanSome(argAp), ap, config) + fwdFlowOut(call, out, true, apSome(argAp), ap, config) } /** @@ -905,7 +907,7 @@ private module Stage2 { fwdFlow(node, _, _, false, config) and config.isSink(node) and toReturn = false and - returnAp = TBooleanNone() and + returnAp = apNone() and ap = false or fwdFlow(node, _, _, unbindBool(ap), unbind(config)) and @@ -925,14 +927,14 @@ private module Stage2 { jumpStep(node, mid, config) and revFlow(mid, _, _, ap, config) and toReturn = false and - returnAp = TBooleanNone() + returnAp = apNone() ) or exists(Node mid | additionalJumpStep(node, mid, config) and revFlow(mid, _, _, ap, config) and toReturn = false and - returnAp = TBooleanNone() and + returnAp = apNone() and ap = false ) or @@ -964,9 +966,9 @@ private module Stage2 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, TBooleanSome(_), unbindBool(ap), config) - then returnAp = TBooleanSome(ap) - else returnAp = TBooleanNone() + if fwdFlow(node, true, apSome(_), unbindBool(ap), config) + then returnAp = apSome(ap) + else returnAp = apNone() ) } @@ -1048,7 +1050,7 @@ private module Stage2 { private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, boolean returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TBooleanSome(returnAp), ap, config) + revFlowIn(call, arg, true, apSome(returnAp), ap, config) } /** @@ -1060,7 +1062,7 @@ private module Stage2 { ) { exists(ReturnNodeExt ret | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, true, TBooleanSome(_), ap, config) + fwdFlow(ret, true, apSome(_), ap, config) ) } @@ -1231,6 +1233,10 @@ private module Stage3 { class ApOption = AccessPathFrontOption; + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + class Cc = boolean; /* Begin: Stage 3 logic. */ @@ -1254,7 +1260,7 @@ private module Stage3 { Stage2::revFlow(node, _, _, false, config) and config.isSource(node) and cc = false and - argAp = TAccessPathFrontNone() and + argAp = apNone() and ap = TFrontNil(getNodeType(node)) or exists(Node mid | @@ -1272,7 +1278,7 @@ private module Stage3 { Stage2::revFlow(node, unbind(config)) and jumpStep(mid, node, config) and cc = false and - argAp = TAccessPathFrontNone() + argAp = apNone() ) or exists(Node mid, AccessPathFrontNil nil | @@ -1280,7 +1286,7 @@ private module Stage3 { Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and cc = false and - argAp = TAccessPathFrontNone() and + argAp = apNone() and ap = TFrontNil(getNodeType(node)) ) or @@ -1304,8 +1310,8 @@ private module Stage3 { fwdFlowIn(_, node, _, _, ap, config) and cc = true and if Stage2::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) - then argAp = TAccessPathFrontSome(ap) - else argAp = TAccessPathFrontNone() + then argAp = apSome(ap) + else argAp = apNone() or // flow out of a callable exists(DataFlowCall call | @@ -1374,7 +1380,7 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, true, TAccessPathFrontSome(argAp), ap, config) + fwdFlowOut(call, node, true, apSome(argAp), ap, config) } /** @@ -1411,7 +1417,7 @@ private module Stage3 { fwdFlow(node, _, _, ap, config) and config.isSink(node) and toReturn = false and - returnAp = TAccessPathFrontNone() and + returnAp = apNone() and ap instanceof AccessPathFrontNil or exists(Node mid | @@ -1430,7 +1436,7 @@ private module Stage3 { jumpStep(node, mid, config) and revFlow(mid, _, _, ap, config) and toReturn = false and - returnAp = TAccessPathFrontNone() + returnAp = apNone() ) or exists(Node mid, AccessPathFrontNil nil | @@ -1438,7 +1444,7 @@ private module Stage3 { additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and - returnAp = TAccessPathFrontNone() and + returnAp = apNone() and ap instanceof AccessPathFrontNil ) or @@ -1468,9 +1474,7 @@ private module Stage3 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, _, ap, config) - then returnAp = TAccessPathFrontSome(ap) - else returnAp = TAccessPathFrontNone() + if fwdFlow(node, true, _, ap, config) then returnAp = apSome(ap) else returnAp = apNone() } pragma[nomagic] @@ -1536,7 +1540,7 @@ private module Stage3 { private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TAccessPathFrontSome(returnAp), ap, config) + revFlowIn(call, arg, true, apSome(returnAp), ap, config) } /** @@ -1548,7 +1552,7 @@ private module Stage3 { ) { exists(ReturnNodeExt ret | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, true, TAccessPathFrontSome(_), ap, config) + fwdFlow(ret, true, apSome(_), ap, config) ) } /* End: Stage 3 logic. */ @@ -1764,6 +1768,10 @@ private module Stage4 { class ApOption = AccessPathApproxOption; + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + class Cc = CallContext; /* Begin: Stage 4 logic. */ @@ -1784,7 +1792,7 @@ private module Stage4 { Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and cc instanceof CallContextAny and - argAp = TAccessPathApproxNone() and + argAp = apNone() and ap = TNil(getNodeType(node)) or Stage3::revFlow(node, _, _, _, unbind(config)) and @@ -1804,14 +1812,14 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and cc instanceof CallContextAny and - argAp = TAccessPathApproxNone() + argAp = apNone() ) or exists(Node mid, AccessPathApproxNil nil | fwdFlow(mid, _, _, nil, config) and additionalJumpStep(mid, node, config) and cc instanceof CallContextAny and - argAp = TAccessPathApproxNone() and + argAp = apNone() and ap = TNil(getNodeType(node)) ) ) @@ -1829,9 +1837,7 @@ private module Stage4 { exists(ApApprox apa | fwdFlowIn(_, node, _, cc, _, ap, config) and apa = ap.getFront() and - if Stage3::revFlow(node, true, _, apa, config) - then argAp = TAccessPathApproxSome(ap) - else argAp = TAccessPathApproxNone() + if Stage3::revFlow(node, true, _, apa, config) then argAp = apSome(ap) else argAp = apNone() ) or // flow out of a callable @@ -1961,7 +1967,7 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argAp), ap, config) + fwdFlowOut(call, node, any(CallContextCall ccc), _, apSome(argAp), ap, config) } /** @@ -1996,7 +2002,7 @@ private module Stage4 { fwdFlow(node, _, _, ap, config) and config.isSink(node) and toReturn = false and - returnAp = TAccessPathApproxNone() and + returnAp = apNone() and ap instanceof AccessPathApproxNil or exists(Node mid | @@ -2015,7 +2021,7 @@ private module Stage4 { jumpStep(node, mid, config) and revFlow(mid, _, _, ap, config) and toReturn = false and - returnAp = TAccessPathApproxNone() + returnAp = apNone() ) or exists(Node mid, AccessPathApproxNil nil | @@ -2023,7 +2029,7 @@ private module Stage4 { additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and - returnAp = TAccessPathApproxNone() and + returnAp = apNone() and ap instanceof AccessPathApproxNil ) or @@ -2053,9 +2059,9 @@ private module Stage4 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, any(CallContextCall ccc), TAccessPathApproxSome(_), ap, config) - then returnAp = TAccessPathApproxSome(ap) - else returnAp = TAccessPathApproxNone() + if fwdFlow(node, any(CallContextCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() } pragma[nomagic] @@ -2128,7 +2134,7 @@ private module Stage4 { private predicate revFlowInToReturn( DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, TAccessPathApproxSome(returnAp), ap, config) + revFlowIn(call, arg, true, apSome(returnAp), ap, config) } /** @@ -2140,7 +2146,7 @@ private module Stage4 { ) { exists(ReturnNodeExt ret, CallContextCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, ccc, TAccessPathApproxSome(_), ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and ccc.matchesCall(call) ) } From 60b51011b9567cab47d843ae2d0eac863ac1fe72 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 22 Oct 2020 10:29:45 +0200 Subject: [PATCH 18/97] Dataflow: Minor refactor of Stage2::revFlow. --- .../java/dataflow/internal/DataFlowImpl.qll | 125 +++++++++--------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index ead71f43b75..1ce36be261d 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -904,72 +904,77 @@ private module Stage2 { * records whether a field must be read from the returned value. */ predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { - fwdFlow(node, _, _, false, config) and + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and config.isSink(node) and toReturn = false and returnAp = apNone() and ap = false or - fwdFlow(node, _, _, unbindBool(ap), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnAp, ap, config) - ) - or - exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnAp, ap, config) and - ap = false - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and - toReturn = false and - returnAp = apNone() - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and - toReturn = false and - returnAp = apNone() and - ap = false - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, returnAp, ap, config) and - revFlowIsRead(c, ap, config) - ) - or - // read - exists(Node mid, Content c, Ap ap0 | - read(node, c, mid, config) and - fwdFlowIsStored(c, unbindBool(ap0), unbind(config)) and - revFlow(mid, toReturn, returnAp, ap0, config) and - ap = true - ) - or - // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(boolean returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) - ) - or - // flow out of a callable - revFlowOut(_, node, _, _, ap, config) and - toReturn = true and - if fwdFlow(node, true, apSome(_), unbindBool(ap), config) - then returnAp = apSome(ap) - else returnAp = apNone() + exists(Node mid | + localFlowStepNodeCand1(node, mid, config) and + revFlow(mid, toReturn, returnAp, ap, config) ) + or + exists(Node mid | + additionalLocalFlowStepNodeCand1(node, mid, config) and + revFlow(mid, toReturn, returnAp, ap, config) and + ap = false + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() and + ap = false + ) + or + // store + exists(Content c | + revFlowStore(c, node, toReturn, returnAp, ap, config) and + revFlowIsRead(c, ap, config) + ) + or + // read + exists(Node mid, Content c, Ap ap0 | + read(node, c, mid, config) and + fwdFlowIsStored(c, unbindBool(ap0), unbind(config)) and + revFlow(mid, toReturn, returnAp, ap0, config) and + ap = true + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(boolean returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, true, apSome(_), unbindBool(ap), config) + then returnAp = apSome(ap) + else returnAp = apNone() } /** From 0a60a3abb3510f4a68e923a42b425b059760484e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 22 Oct 2020 11:04:48 +0200 Subject: [PATCH 19/97] Dataflow: Align on ApNil. --- .../java/dataflow/internal/DataFlowImpl.qll | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 1ce36be261d..f0b2570b86f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -745,6 +745,10 @@ private module Stage2 { class Ap = boolean; + class ApNil extends Ap { + ApNil() { this = false } + } + class ApOption = BooleanOption; ApOption apNone() { result = TBooleanNone() } @@ -858,7 +862,7 @@ private module Stage2 { fwdFlow(arg, cc, argAp, ap, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | - ap = false or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -870,7 +874,7 @@ private module Stage2 { fwdFlow(ret, cc, argAp, ap, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | - ap = false or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -903,6 +907,7 @@ private module Stage2 { * the enclosing callable in order to reach a sink, and if so, `returnAp` * records whether a field must be read from the returned value. */ + pragma[nomagic] predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { revFlow0(node, toReturn, returnAp, ap, config) and fwdFlow(node, _, _, ap, config) @@ -916,17 +921,18 @@ private module Stage2 { config.isSink(node) and toReturn = false and returnAp = apNone() and - ap = false + ap instanceof ApNil or exists(Node mid | localFlowStepNodeCand1(node, mid, config) and revFlow(mid, toReturn, returnAp, ap, config) ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalLocalFlowStepNodeCand1(node, mid, config) and - revFlow(mid, toReturn, returnAp, ap, config) and - ap = false + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | @@ -936,12 +942,13 @@ private module Stage2 { returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and returnAp = apNone() and - ap = false + ap instanceof ApNil ) or // store @@ -963,7 +970,7 @@ private module Stage2 { revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnAp0 | + exists(Ap returnAp0 | revFlowInToReturn(call, node, returnAp0, ap, config) and revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) @@ -1034,7 +1041,7 @@ private module Stage2 { revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) | - ap = false or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1047,13 +1054,13 @@ private module Stage2 { revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) | - ap = false or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnAp, Ap ap, Configuration config + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config ) { revFlowIn(call, arg, true, apSome(returnAp), ap, config) } @@ -1236,6 +1243,8 @@ private module Stage3 { class Ap = AccessPathFront; + class ApNil = AccessPathFrontNil; + class ApOption = AccessPathFrontOption; ApOption apNone() { result = TAccessPathFrontNone() } @@ -1273,7 +1282,7 @@ private module Stage3 { localFlowBigStep(mid, node, true, _, config, _) ) or - exists(Node mid, AccessPathFrontNil nil | + exists(Node mid, ApNil nil | fwdFlow(mid, cc, argAp, nil, config) and localFlowBigStep(mid, node, false, ap, config, _) ) @@ -1286,7 +1295,7 @@ private module Stage3 { argAp = apNone() ) or - exists(Node mid, AccessPathFrontNil nil | + exists(Node mid, ApNil nil | fwdFlow(mid, _, _, nil, config) and Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and @@ -1365,7 +1374,7 @@ private module Stage3 { fwdFlow(arg, cc, argAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - ap instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1377,7 +1386,7 @@ private module Stage3 { fwdFlow(ret, cc, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) | - ap instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1423,18 +1432,18 @@ private module Stage3 { config.isSink(node) and toReturn = false and returnAp = apNone() and - ap instanceof AccessPathFrontNil + ap instanceof ApNil or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and revFlow(mid, toReturn, returnAp, ap, config) ) or - exists(Node mid, AccessPathFrontNil nil | + exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and localFlowBigStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and - ap instanceof AccessPathFrontNil + ap instanceof ApNil ) or exists(Node mid | @@ -1444,13 +1453,13 @@ private module Stage3 { returnAp = apNone() ) or - exists(Node mid, AccessPathFrontNil nil | + exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and returnAp = apNone() and - ap instanceof AccessPathFrontNil + ap instanceof ApNil ) or // store @@ -1524,7 +1533,7 @@ private module Stage3 { revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | - ap instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1537,7 +1546,7 @@ private module Stage3 { revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - ap instanceof AccessPathFrontNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1771,6 +1780,8 @@ private module Stage4 { class Ap = AccessPathApprox; + class ApNil = AccessPathApproxNil; + class ApOption = AccessPathApproxOption; ApOption apNone() { result = TAccessPathApproxNone() } @@ -1807,10 +1818,10 @@ private module Stage4 { localFlowBigStep(mid, node, true, _, config, localCC) ) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC, AccessPathFront apf | + exists(Node mid, ApNil nil, LocalCallContext localCC, AccessPathFront apf | fwdFlowLocalEntry(mid, cc, argAp, nil, localCC, config) and localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = ap.(AccessPathApproxNil).getFront() + apf = ap.(ApNil).getFront() ) or exists(Node mid | @@ -1820,7 +1831,7 @@ private module Stage4 { argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | + exists(Node mid, ApNil nil | fwdFlow(mid, _, _, nil, config) and additionalJumpStep(mid, node, config) and cc instanceof CallContextAny and @@ -1944,7 +1955,7 @@ private module Stage4 { then innercc = TSpecificCall(call) else innercc = TSomeCall() | - ap instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -1964,7 +1975,7 @@ private module Stage4 { innercc.(CallContextCall).matchesCall(call) ) | - ap instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -2008,18 +2019,18 @@ private module Stage4 { config.isSink(node) and toReturn = false and returnAp = apNone() and - ap instanceof AccessPathApproxNil + ap instanceof ApNil or exists(Node mid | localFlowBigStep(node, mid, true, _, config, _) and revFlow(mid, toReturn, returnAp, ap, config) ) or - exists(Node mid, AccessPathApproxNil nil | + exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and localFlowBigStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and - ap instanceof AccessPathApproxNil + ap instanceof ApNil ) or exists(Node mid | @@ -2029,13 +2040,13 @@ private module Stage4 { returnAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | + exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and revFlow(mid, _, _, nil, config) and toReturn = false and returnAp = apNone() and - ap instanceof AccessPathApproxNil + ap instanceof ApNil ) or // store @@ -2118,7 +2129,7 @@ private module Stage4 { revFlow(out, toReturn, returnAp, ap, config) and flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) | - ap instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } @@ -2131,7 +2142,7 @@ private module Stage4 { revFlow(p, toReturn, returnAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) | - ap instanceof AccessPathApproxNil or allowsFieldFlow = true + ap instanceof ApNil or allowsFieldFlow = true ) } From bfd8a3d104cb12b3812c319999f5e0e51d39ca7e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 22 Oct 2020 11:30:09 +0200 Subject: [PATCH 20/97] Dataflow: Rename stage 2 cons-cand predicates. --- .../code/java/dataflow/internal/DataFlowImpl.qll | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index f0b2570b86f..1c2ac65a782 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -812,7 +812,7 @@ private module Stage2 { // read exists(Content c | fwdFlowRead(c, node, cc, argAp, config) and - fwdFlowIsStored(c, ap, config) + fwdFlowConsCand(c, ap, config) ) or // flow into a callable @@ -837,7 +837,7 @@ private module Stage2 { * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ pragma[noinline] - private predicate fwdFlowIsStored(Content c, Ap ap, Configuration config) { + private predicate fwdFlowConsCand(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and Stage1::revFlow(node, unbind(config)) and @@ -954,13 +954,13 @@ private module Stage2 { // store exists(Content c | revFlowStore(c, node, toReturn, returnAp, ap, config) and - revFlowIsRead(c, ap, config) + revFlowConsCand(c, ap, config) ) or // read exists(Node mid, Content c, Ap ap0 | read(node, c, mid, config) and - fwdFlowIsStored(c, unbindBool(ap0), unbind(config)) and + fwdFlowConsCand(c, unbindBool(ap0), unbind(config)) and revFlow(mid, toReturn, returnAp, ap0, config) and ap = true ) @@ -988,12 +988,12 @@ private module Stage2 { * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[noinline] - private predicate revFlowIsRead(Content c, Ap ap, Configuration config) { + private predicate revFlowConsCand(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and fwdFlow(node, _, _, true, unbind(config)) and read(node, c, mid, config) and - fwdFlowIsStored(c, unbindBool(ap), unbind(config)) and + fwdFlowConsCand(c, unbindBool(ap), unbind(config)) and revFlow(mid, _, _, ap, config) ) } @@ -1028,7 +1028,7 @@ private module Stage2 { predicate revFlowIsReadAndStored(Content c, Configuration conf) { exists(boolean apNonEmpty | revFlowIsStored(c, apNonEmpty, conf) and - revFlowIsRead(c, apNonEmpty, conf) + revFlowConsCand(c, apNonEmpty, conf) ) } From 628e0a795aac08cc634ba35fc3c8400ed2b138ce Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 22 Oct 2020 12:19:53 +0200 Subject: [PATCH 21/97] Dataflow: A few variable renamings. --- .../java/dataflow/internal/DataFlowImpl.qll | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 1c2ac65a782..2c74d0ccde7 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -825,9 +825,9 @@ private module Stage2 { fwdFlowOut(call, node, cc, argAp, ap, config) and cc = false or - exists(boolean argStored0 | - fwdFlowOutFromArg(call, node, argStored0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) ) @@ -868,11 +868,11 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node node, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow | fwdFlow(ret, cc, argAp, ap, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) + flowOutOfCallNodeCand1(call, ret, node, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -880,9 +880,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node out, boolean argAp, Ap ap, Configuration config + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, true, apSome(argAp), ap, config) + fwdFlowOut(call, node, true, apSome(argAp), ap, config) } /** @@ -1026,9 +1026,9 @@ private module Stage2 { */ pragma[noinline] predicate revFlowIsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - revFlowIsStored(c, apNonEmpty, conf) and - revFlowConsCand(c, apNonEmpty, conf) + exists(Ap ap | + revFlowIsStored(c, ap, conf) and + revFlowConsCand(c, ap, conf) ) } @@ -1332,9 +1332,9 @@ private module Stage3 { fwdFlowOut(call, node, cc, argAp, ap, config) and cc = false or - exists(Ap argApf0 | - fwdFlowOutFromArg(call, node, argApf0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argApf0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) } @@ -2066,9 +2066,9 @@ private module Stage4 { revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(Ap returnApa0 | - revFlowInToReturn(call, node, returnApa0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnApa0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or From 261ef0fbff76f175d896bd5bc011bcccb824f139 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 26 Oct 2020 15:39:39 +0100 Subject: [PATCH 22/97] Dataflow: Refactor forward stores and remove some useless conjuncts. --- .../java/dataflow/internal/DataFlowImpl.qll | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 2c74d0ccde7..7af4b710c00 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -481,7 +481,6 @@ private module Stage1 { pragma[nomagic] private predicate revFlowIsRead(Content c, Configuration config) { exists(Node mid, Node node | - useFieldFlow(config) and fwdFlow(node, unbind(config)) and read(node, c, mid) and fwdFlowIsStored(c, unbind(config)) and @@ -839,8 +838,6 @@ private module Stage2 { pragma[noinline] private predicate fwdFlowConsCand(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | - useFieldFlow(config) and - Stage1::revFlow(node, unbind(config)) and fwdFlow(mid, _, _, ap, config) and storeCand1(mid, c, node, config) ) @@ -990,7 +987,6 @@ private module Stage2 { pragma[noinline] private predicate revFlowConsCand(Content c, Ap ap, Configuration config) { exists(Node mid, Node node | - useFieldFlow(config) and fwdFlow(node, _, _, true, unbind(config)) and read(node, c, mid, config) and fwdFlowConsCand(c, unbindBool(ap), unbind(config)) and @@ -1305,12 +1301,9 @@ private module Stage3 { ) or // store - exists(Node mid, TypedContent tc, Ap ap0, DataFlowType contentType | - fwdFlow(mid, cc, argAp, ap0, config) and - storeCand2(mid, tc, node, contentType, config) and - Stage2::revFlow(node, _, _, true, unbind(config)) and - ap.headUsesContent(tc) and - compatibleTypes(ap0.getType(), contentType) + exists(TypedContent tc | + fwdFlowStore(node, tc, _, cc, argAp, config) and + ap.headUsesContent(tc) ) or // read @@ -1340,15 +1333,23 @@ private module Stage3 { } pragma[nomagic] - private predicate fwdFlowConsCand(TypedContent tc, Ap ap, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - fwdFlow(mid, _, _, ap, config) and - storeCand2(mid, tc, n, contentType, config) and - Stage2::revFlow(n, _, _, true, unbind(config)) and - compatibleTypes(ap.getType(), contentType) + private predicate fwdFlowStore( + Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config + ) { + exists(Node mid, DataFlowType contentType | + fwdFlow(mid, cc, argAp, ap0, config) and + storeCand2(mid, tc, node, contentType, config) and + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap0.getType(), contentType) ) } + pragma[nomagic] + private predicate fwdFlowConsCand(TypedContent tc, Ap ap, Configuration config) { + fwdFlowStore(_, tc, ap, _, _, config) + } + pragma[nomagic] private predicate fwdFlowRead0( Node node1, TypedContent tc, Content c, Node node2, Cc cc, ApOption argAp, @@ -1889,6 +1890,14 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate fwdFlowConsCand( + TypedContent tc, AccessPathFront apf, Ap ap, Configuration config + ) { + fwdFlowStore(_, tc, ap, _, _, config) and + apf = ap.getFront() + } + pragma[nomagic] private predicate storeCand( Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, @@ -1929,17 +1938,6 @@ private module Stage4 { ) } - pragma[nomagic] - private predicate fwdFlowConsCand( - TypedContent tc, AccessPathFront apf, Ap ap, Configuration config - ) { - exists(Node n | - fwdFlow(n, _, _, ap, config) and - apf = ap.getFront() and - fwdFlowStore0(n, tc, _, apf, config) - ) - } - pragma[nomagic] private predicate fwdFlowIn( DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, From b0e5925feaf1bde4d02e7831b7bc32a7a15b37b1 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 28 Oct 2020 10:16:19 +0100 Subject: [PATCH 23/97] Dataflow: Refactor stage 3 conscand predicates. --- .../java/dataflow/internal/DataFlowImpl.qll | 110 ++++++++++-------- .../dataflow/internal/DataFlowImplCommon.qll | 3 + 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 7af4b710c00..1e31f045ee9 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1241,6 +1241,12 @@ private module Stage3 { class ApNil = AccessPathFrontNil; + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + class ApOption = AccessPathFrontOption; ApOption apNone() { result = TAccessPathFrontNone() } @@ -1301,15 +1307,15 @@ private module Stage3 { ) or // store - exists(TypedContent tc | - fwdFlowStore(node, tc, _, cc, argAp, config) and - ap.headUsesContent(tc) + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(node, tc, ap0, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(TypedContent tc | - fwdFlowRead(tc, node, cc, argAp, config) and - fwdFlowConsCand(tc, ap, config) and + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) and Stage2::revFlow(node, _, _, unbindBool(ap.toBoolNonEmpty()), unbind(config)) ) or @@ -1346,25 +1352,21 @@ private module Stage3 { } pragma[nomagic] - private predicate fwdFlowConsCand(TypedContent tc, Ap ap, Configuration config) { - fwdFlowStore(_, tc, ap, _, _, config) - } - - pragma[nomagic] - private predicate fwdFlowRead0( - Node node1, TypedContent tc, Content c, Node node2, Cc cc, ApOption argAp, - AccessPathFrontHead ap, Configuration config - ) { - fwdFlow(node1, cc, argAp, ap, config) and - readCand2(node1, c, node2, config) and - ap.headUsesContent(tc) + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tc, tail, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) } pragma[nomagic] private predicate fwdFlowRead( - TypedContent tc, Node node, Cc cc, ApOption argAp, Configuration config + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { - fwdFlowRead0(_, tc, tc.getContent(), node, cc, argAp, _, config) + fwdFlow(node1, cc, argAp, ap, config) and + readCand2(node1, c, node2, config) and + getHeadContent(ap) = c } pragma[nomagic] @@ -1411,6 +1413,11 @@ private module Stage3 { ) } + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + /** * Holds if `node` with access path front `ap` is part of a path from a * source to a sink in the configuration `config`. @@ -1464,15 +1471,15 @@ private module Stage3 { ) or // store - exists(TypedContent tc | - revFlowStore(node, tc, ap, toReturn, returnAp, config) and - revFlowConsCand(tc, ap, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, node, ap, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(TypedContent tc, Ap ap0 | - revFlowRead(node, tc, ap, toReturn, returnAp, ap0, config) and - fwdFlowConsCand(tc, ap0, config) + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable @@ -1494,35 +1501,29 @@ private module Stage3 { pragma[nomagic] predicate readCandFwd(Node node1, TypedContent tc, Ap ap, Node node2, Configuration config) { - fwdFlowRead0(node1, tc, tc.getContent(), node2, _, _, ap, config) - } - - pragma[nomagic] - private predicate revFlowRead( - Node node, TypedContent tc, Ap ap, boolean toReturn, ApOption returnAp, Ap apf0, - Configuration config - ) { - exists(Node mid | - readCandFwd(node, tc, ap, mid, config) and - revFlow(mid, toReturn, returnAp, apf0, config) - ) + fwdFlowRead(ap, _, node1, node2, _, _, config) and + ap.headUsesContent(tc) } pragma[nomagic] private predicate revFlowStore( - Node node, TypedContent tc, Ap ap, boolean toReturn, ApOption returnAp, Configuration config + Ap ap0, Content c, Node node, Ap ap, boolean toReturn, ApOption returnAp, Configuration config ) { - exists(Node mid | + exists(Node mid, TypedContent tc | fwdFlow(node, _, _, ap, config) and storeCand2(node, tc, mid, _, unbind(config)) and - revFlow(mid, toReturn, returnAp, TFrontHead(tc), unbind(config)) + revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and + ap0 = TFrontHead(tc) and + tc.getContent() = c ) } pragma[nomagic] - predicate revFlowConsCand(TypedContent tc, Ap ap, Configuration config) { - fwdFlowConsCand(tc, ap, config) and - revFlowRead(_, tc, _, _, _, ap, config) + predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) } pragma[nomagic] @@ -1573,6 +1574,13 @@ private module Stage3 { /* End: Stage 3 logic. */ } +private predicate stage3consCand(TypedContent tc, AccessPathFront apf, Configuration config) { + exists(AccessPathFront apf0 | + Stage3::revFlowConsCand(apf0, _, apf, config) and + apf0.getHead() = tc + ) +} + /** * Holds if `argApf` is recorded as the summary context for flow reaching `node` * and remains relevant for the following pruning stage. @@ -1590,7 +1598,7 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage3::revFlowConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | stage3consCand(tc, apf, config)) and nodes = strictcount(Node n | Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) @@ -1606,11 +1614,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - Stage3::revFlowConsCand(tc, TFrontNil(t), _) and + stage3consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage3::revFlowConsCand(tc1, TFrontHead(tc2), _) and + stage3consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1740,7 +1748,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | Stage3::revFlowConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | stage3consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1751,7 +1759,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - Stage3::revFlowConsCand(tc, TFrontNil(t), _) and + stage3consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1914,7 +1922,7 @@ private module Stage4 { ) { exists(AccessPathFront apf | storeCand(mid, tc, node, apf0, apf, config) and - Stage3::revFlowConsCand(tc, apf0, config) and + stage3consCand(tc, apf0, config) and Stage3::revFlow(node, _, _, apf, unbind(config)) ) } @@ -1934,7 +1942,7 @@ private module Stage4 { exists(Node mid, TypedContent tc | fwdFlowRead0(mid, tc, ap0, node, cc, argAp, config) and Stage3::revFlow(node, _, _, apf, unbind(config)) and - Stage3::revFlowConsCand(tc, apf, unbind(config)) + stage3consCand(tc, apf, unbind(config)) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index efde95bd16a..e973ea25c56 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -802,6 +802,9 @@ abstract class AccessPathFront extends TAccessPathFront { abstract boolean toBoolNonEmpty(); + TypedContent getHead() { this = TFrontHead(result) } + + // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } predicate isClearedAt(Node n) { From 2afc572a342fc7f012745535a5fe993df6dc2edc Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 28 Oct 2020 14:56:40 +0100 Subject: [PATCH 24/97] Dataflow: Refactor stage 2 read and stores. --- .../java/dataflow/internal/DataFlowImpl.qll | 98 +++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 1e31f045ee9..23bd2316b75 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -601,8 +601,8 @@ private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration c } pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | +private predicate storeCand1(Node n1, TypedContent tc, Node n2, Configuration config) { + exists(Content c | Stage1::revFlowIsReadAndStored(c, config) and Stage1::revFlow(n2, unbind(config)) and store(n1, tc, n2, _) and @@ -748,6 +748,12 @@ private module Stage2 { ApNil() { this = false } } + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + class ApOption = BooleanOption; ApOption apNone() { result = TBooleanNone() } @@ -802,16 +808,15 @@ private module Stage2 { ) or // store - exists(Node mid | - fwdFlow(mid, cc, argAp, _, config) and - storeCand1(mid, _, node, config) and - ap = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(node, tc, ap0, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - fwdFlowRead(c, node, cc, argAp, config) and - fwdFlowConsCand(c, ap, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable @@ -832,23 +837,35 @@ private module Stage2 { ) } + pragma[nomagic] + private predicate fwdFlowStore( + Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config + ) { + exists(Node mid | + fwdFlow(mid, cc, argAp, ap0, config) and + storeCand1(mid, tc, node, config) + ) + } + /** * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ - pragma[noinline] - private predicate fwdFlowConsCand(Content c, Ap ap, Configuration config) { - exists(Node mid, Node node | - fwdFlow(mid, _, _, ap, config) and - storeCand1(mid, c, node, config) + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tc, tail, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) ) } pragma[nomagic] - private predicate fwdFlowRead(Content c, Node node, Cc cc, ApOption argAp, Configuration config) { - exists(Node mid | - fwdFlow(mid, cc, argAp, true, config) and - read(mid, c, node, config) - ) + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + read(node1, c, node2, config) and + getHeadContent(ap) = c } pragma[nomagic] @@ -895,6 +912,11 @@ private module Stage2 { ) } + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + /** * Holds if `node` is part of a path from a source to a sink in the * configuration `config`. The Boolean `ap` records whether the tracked @@ -949,17 +971,15 @@ private module Stage2 { ) or // store - exists(Content c | - revFlowStore(c, node, toReturn, returnAp, ap, config) and - revFlowConsCand(c, ap, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, Ap ap0 | - read(node, c, mid, config) and - fwdFlowConsCand(c, unbindBool(ap0), unbind(config)) and + exists(Node mid, Ap ap0 | revFlow(mid, toReturn, returnAp, ap0, config) and - ap = true + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable @@ -984,23 +1004,23 @@ private module Stage2 { /** * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ - pragma[noinline] - private predicate revFlowConsCand(Content c, Ap ap, Configuration config) { - exists(Node mid, Node node | - fwdFlow(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - fwdFlowConsCand(c, unbindBool(ap), unbind(config)) and - revFlow(mid, _, _, ap, config) + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) ) } pragma[nomagic] private predicate revFlowStore( - Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(Node mid | - storeCand1(node, c, mid, config) and - revFlow(mid, toReturn, returnAp, true, config) and + exists(Node mid, TypedContent tc | + storeCand1(node, tc, mid, config) and + tc.getContent() = c and + revFlow(mid, toReturn, returnAp, ap0, config) and + ap0 = true and fwdFlow(node, _, _, ap, unbind(config)) ) } @@ -1011,7 +1031,7 @@ private module Stage2 { pragma[nomagic] private predicate revFlowIsStored(Content c, Ap ap, Configuration conf) { exists(Node node | - revFlowStore(c, node, _, _, ap, conf) and + revFlowStore(_, c, node, _, _, ap, conf) and revFlow(node, _, _, ap, conf) ) } @@ -1024,7 +1044,7 @@ private module Stage2 { predicate revFlowIsReadAndStored(Content c, Configuration conf) { exists(Ap ap | revFlowIsStored(c, ap, conf) and - revFlowConsCand(c, ap, conf) + revFlowConsCand(_, c, ap, conf) ) } From 00d726de3fbe4ff19d139f937d60ec464aa22cae Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 29 Oct 2020 13:18:17 +0100 Subject: [PATCH 25/97] Dataflow: Refactor stage 4 read and stores. --- .../java/dataflow/internal/DataFlowImpl.qll | 116 ++++++++++-------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 23bd2316b75..87135c27069 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1519,6 +1519,7 @@ private module Stage3 { if fwdFlow(node, true, _, ap, config) then returnAp = apSome(ap) else returnAp = apNone() } + // TODO: remove pragma[nomagic] predicate readCandFwd(Node node1, TypedContent tc, Ap ap, Node node2, Configuration config) { fwdFlowRead(ap, _, node1, node2, _, _, config) and @@ -1591,6 +1592,13 @@ private module Stage3 { fwdFlow(ret, true, apSome(_), ap, config) ) } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap | + revFlow(node2, _, _, ap, config) and + readStepFwd(node1, _, c, node2, ap, config) + ) + } /* End: Stage 3 logic. */ } @@ -1811,6 +1819,12 @@ private module Stage4 { class ApNil = AccessPathApproxNil; + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + class ApOption = AccessPathApproxOption; ApOption apNone() { result = TAccessPathApproxNone() } @@ -1870,12 +1884,15 @@ private module Stage4 { ) or // store - exists(TypedContent tc | fwdFlowStore(node, tc, pop(tc, ap), cc, argAp, config)) + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(node, tc, ap0, cc, argAp, config) and + ap = apCons(tc, ap0) + ) or // read - exists(TypedContent tc, AccessPathFront apf | - fwdFlowRead(node, push(tc, ap), apf, cc, argAp, config) and - fwdFlowConsCand(tc, apf, ap, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable @@ -1919,11 +1936,12 @@ private module Stage4 { } pragma[nomagic] - private predicate fwdFlowConsCand( - TypedContent tc, AccessPathFront apf, Ap ap, Configuration config - ) { - fwdFlowStore(_, tc, ap, _, _, config) and - apf = ap.getFront() + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tc, tail, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) } pragma[nomagic] @@ -1947,23 +1965,13 @@ private module Stage4 { ) } - pragma[nomagic] - private predicate fwdFlowRead0( - Node node1, TypedContent tc, Ap ap0, Node node2, Cc cc, ApOption argAp, Configuration config - ) { - fwdFlow(node1, cc, argAp, ap0, config) and - Stage3::readCandFwd(node1, tc, ap0.getFront(), node2, config) - } - pragma[nomagic] private predicate fwdFlowRead( - Node node, Ap ap0, AccessPathFront apf, Cc cc, ApOption argAp, Configuration config + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { - exists(Node mid, TypedContent tc | - fwdFlowRead0(mid, tc, ap0, node, cc, argAp, config) and - Stage3::revFlow(node, _, _, apf, unbind(config)) and - stage3consCand(tc, apf, unbind(config)) - ) + fwdFlow(node1, cc, argAp, ap, config) and + Stage3::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c } pragma[nomagic] @@ -2025,6 +2033,11 @@ private module Stage4 { ) } + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + /** * Holds if `node` with approximate access path `ap` is part of a path from a * source to a sink in the configuration `config`. @@ -2033,11 +2046,13 @@ private module Stage4 { * the enclosing callable in order to reach a sink, and if so, `returnAp` * records the approximate access path of the returned value. */ + pragma[nomagic] predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { revFlow0(node, toReturn, returnAp, ap, config) and fwdFlow(node, _, _, ap, config) } + pragma[nomagic] private predicate revFlow0( Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { @@ -2076,15 +2091,15 @@ private module Stage4 { ) or // store - exists(TypedContent tc | - revFlowStore(tc, node, toReturn, returnAp, ap, config) and - revFlowConsCand(tc, ap, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read exists(Node mid, Ap ap0 | - readFlowFwd(node, _, mid, ap, ap0, config) and - revFlow(mid, toReturn, returnAp, ap0, config) + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable @@ -2117,32 +2132,20 @@ private module Stage4 { pragma[nomagic] private predicate revFlowStore( - TypedContent tc, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(Node mid, Ap ap0 | + exists(Node mid, TypedContent tc | storeFlowFwd(node, tc, mid, ap, ap0, config) and - revFlow(mid, toReturn, returnAp, ap0, config) + revFlow(mid, toReturn, returnAp, ap0, config) and + tc.getContent() = c ) } pragma[nomagic] - private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config - ) { - exists(AccessPathFrontHead apf | - Stage3::readCandFwd(node1, tc, apf, node2, config) and - apf = ap.getFront() and - fwdFlowRead(node2, ap, _, _, _, config) and - ap0 = pop(tc, ap) and - fwdFlowConsCand(tc, _, ap0, unbind(config)) - ) - } - - pragma[nomagic] - predicate revFlowConsCand(TypedContent tc, Ap ap, Configuration config) { - exists(Node n, Node mid | - revFlow(mid, _, _, ap, config) and - readFlowFwd(n, tc, mid, _, ap, config) + predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) ) } @@ -2197,6 +2200,13 @@ private module Stage4 { /* End: Stage 4 logic. */ } +private predicate stage4consCand(TypedContent tc, AccessPathApprox apa, Configuration config) { + exists(AccessPathApprox apa0 | + Stage4::revFlowConsCand(apa0, _, apa, config) and + apa0.getHead() = tc + ) +} + bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } @@ -2273,8 +2283,8 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - Stage4::revFlowConsCand(tc, - any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) + stage4consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + config) ) ) } @@ -2301,7 +2311,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - Stage4::revFlowConsCand(head, result, config) + stage4consCand(head, result, config) ) } @@ -2519,7 +2529,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - Stage4::revFlowConsCand(head1, result.getApprox(), _) and + stage4consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2550,7 +2560,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - Stage4::revFlowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + stage4consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } From d037909c7ba25cf330ad042330d9e2bd883c8122 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 29 Oct 2020 14:22:22 +0100 Subject: [PATCH 26/97] Dataflow: Minor reorderings and renamings. --- .../java/dataflow/internal/DataFlowImpl.qll | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 87135c27069..edd0d451a54 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -748,6 +748,8 @@ private module Stage2 { ApNil() { this = false } } + ApNil getApNil(Node node) { any() } + bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } @@ -762,6 +764,8 @@ private module Stage2 { class Cc = boolean; + Cc ccAny() { result = false } + /* Begin: Stage 2 logic. */ /** * Holds if `node` is reachable from a source in the configuration `config`. @@ -775,9 +779,9 @@ private module Stage2 { private predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage1::revFlow(node, config) and config.isSource(node) and - cc = false and + cc = ccAny() and argAp = apNone() and - ap = false + ap = getApNil(node) or Stage1::revFlow(node, unbind(config)) and ( @@ -795,16 +799,16 @@ private module Stage2 { exists(Node mid | fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and - cc = false and + cc = ccAny() and argAp = apNone() ) or - exists(Node mid | - fwdFlow(mid, _, _, ap, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and additionalJumpStep(mid, node, config) and - cc = false and + cc = ccAny() and argAp = apNone() and - ap = false + ap = getApNil(node) ) or // store @@ -827,7 +831,7 @@ private module Stage2 { // flow out of a callable exists(DataFlowCall call | fwdFlowOut(call, node, cc, argAp, ap, config) and - cc = false + cc = ccAny() or exists(Ap argAp0 | fwdFlowOutFromArg(call, node, argAp0, ap, config) and @@ -1001,6 +1005,19 @@ private module Stage2 { else returnAp = apNone() } + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, returnAp, ap0, config) and + storeCand1(node, tc, mid, config) and + tc.getContent() = c and + ap0 = true and + fwdFlow(node, _, _, ap, unbind(config)) + ) + } + /** * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ @@ -1012,19 +1029,6 @@ private module Stage2 { ) } - pragma[nomagic] - private predicate revFlowStore( - Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config - ) { - exists(Node mid, TypedContent tc | - storeCand1(node, tc, mid, config) and - tc.getContent() = c and - revFlow(mid, toReturn, returnAp, ap0, config) and - ap0 = true and - fwdFlow(node, _, _, ap, unbind(config)) - ) - } - /** * Holds if `c` is the target of a store in the flow covered by `revFlow`. */ @@ -1261,6 +1265,8 @@ private module Stage3 { class ApNil = AccessPathFrontNil; + ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } @@ -1275,6 +1281,8 @@ private module Stage3 { class Cc = boolean; + Cc ccAny() { result = false } + /* Begin: Stage 3 logic. */ /** * Holds if `node` is reachable with access path front `ap` from a @@ -1295,9 +1303,9 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage2::revFlow(node, _, _, false, config) and config.isSource(node) and - cc = false and + cc = ccAny() and argAp = apNone() and - ap = TFrontNil(getNodeType(node)) + ap = getApNil(node) or exists(Node mid | fwdFlow(mid, cc, argAp, ap, config) and @@ -1313,7 +1321,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and Stage2::revFlow(node, unbind(config)) and jumpStep(mid, node, config) and - cc = false and + cc = ccAny() and argAp = apNone() ) or @@ -1321,9 +1329,9 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and Stage2::revFlow(node, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = false and + cc = ccAny() and argAp = apNone() and - ap = TFrontNil(getNodeType(node)) + ap = getApNil(node) ) or // store @@ -1349,7 +1357,7 @@ private module Stage3 { // flow out of a callable exists(DataFlowCall call | fwdFlowOut(call, node, cc, argAp, ap, config) and - cc = false + cc = ccAny() or exists(Ap argAp0 | fwdFlowOutFromArg(call, node, argAp0, ap, config) and @@ -1492,7 +1500,7 @@ private module Stage3 { or // store exists(Ap ap0, Content c | - revFlowStore(ap0, c, node, ap, toReturn, returnAp, config) and + revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and revFlowConsCand(ap0, c, ap, config) ) or @@ -1528,12 +1536,12 @@ private module Stage3 { pragma[nomagic] private predicate revFlowStore( - Ap ap0, Content c, Node node, Ap ap, boolean toReturn, ApOption returnAp, Configuration config + Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and fwdFlow(node, _, _, ap, config) and storeCand2(node, tc, mid, _, unbind(config)) and - revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and ap0 = TFrontHead(tc) and tc.getContent() = c ) @@ -1819,6 +1827,8 @@ private module Stage4 { class ApNil = AccessPathApproxNil; + ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } @@ -1833,6 +1843,8 @@ private module Stage4 { class Cc = CallContext; + Cc ccAny() { result instanceof CallContextAny } + /* Begin: Stage 4 logic. */ /** * Holds if `node` is reachable with approximate access path `ap` from a source @@ -1850,9 +1862,9 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { Stage3::revFlow(node, _, _, _, config) and config.isSource(node) and - cc instanceof CallContextAny and + cc = ccAny() and argAp = apNone() and - ap = TNil(getNodeType(node)) + ap = getApNil(node) or Stage3::revFlow(node, _, _, _, unbind(config)) and ( @@ -1870,16 +1882,16 @@ private module Stage4 { exists(Node mid | fwdFlow(mid, _, _, ap, config) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and + cc = ccAny() and argAp = apNone() ) or exists(Node mid, ApNil nil | fwdFlow(mid, _, _, nil, config) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and + cc = ccAny() and argAp = apNone() and - ap = TNil(getNodeType(node)) + ap = getApNil(node) ) ) or @@ -2135,8 +2147,8 @@ private module Stage4 { Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { exists(Node mid, TypedContent tc | - storeFlowFwd(node, tc, mid, ap, ap0, config) and revFlow(mid, toReturn, returnAp, ap0, config) and + storeFlowFwd(node, tc, mid, ap, ap0, config) and tc.getContent() = c ) } From dc2b2cc13fd7fd6d2139888071d16dbefcb91986 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 30 Oct 2020 15:35:39 +0100 Subject: [PATCH 27/97] Dataflow: Some renamings. --- .../java/dataflow/internal/DataFlowImpl.qll | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index edd0d451a54..1ff4f684a23 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -766,6 +766,10 @@ private module Stage2 { Cc ccAny() { result = false } + predicate flowCand(Node node, ApApprox apa, Configuration config) { + Stage1::revFlow(node, config) and exists(apa) + } + /* Begin: Stage 2 logic. */ /** * Holds if `node` is reachable from a source in the configuration `config`. @@ -777,13 +781,13 @@ private module Stage2 { * value was stored into a field of the argument. */ private predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - Stage1::revFlow(node, config) and + flowCand(node, _, config) and config.isSource(node) and cc = ccAny() and argAp = apNone() and ap = getApNil(node) or - Stage1::revFlow(node, unbind(config)) and + flowCand(node, _, unbind(config)) and ( exists(Node mid | fwdFlow(mid, cc, argAp, ap, config) and @@ -1265,6 +1269,9 @@ private module Stage3 { class ApNil = AccessPathFrontNil; + bindingset[result, ap] + ApApprox getApprox(Ap ap) { result = unbindBool(ap.toBoolNonEmpty()) } + ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } bindingset[tc, tail] @@ -1283,6 +1290,10 @@ private module Stage3 { Cc ccAny() { result = false } + predicate flowCand(Node node, ApApprox apa, Configuration config) { + Stage2::revFlow(node, _, _, apa, config) + } + /* Begin: Stage 3 logic. */ /** * Holds if `node` is reachable with access path front `ap` from a @@ -1295,13 +1306,14 @@ private module Stage3 { pragma[nomagic] predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and not ap.isClearedAt(node) and if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() } pragma[nomagic] private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - Stage2::revFlow(node, _, _, false, config) and + flowCand(node, false, config) and config.isSource(node) and cc = ccAny() and argAp = apNone() and @@ -1319,7 +1331,7 @@ private module Stage3 { or exists(Node mid | fwdFlow(mid, _, _, ap, config) and - Stage2::revFlow(node, unbind(config)) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and cc = ccAny() and argAp = apNone() @@ -1327,7 +1339,7 @@ private module Stage3 { or exists(Node mid, ApNil nil | fwdFlow(mid, _, _, nil, config) and - Stage2::revFlow(node, unbind(config)) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and cc = ccAny() and argAp = apNone() and @@ -1344,7 +1356,7 @@ private module Stage3 { exists(Ap ap0, Content c | fwdFlowRead(ap0, c, _, node, cc, argAp, config) and fwdFlowConsCand(ap0, c, ap, config) and - Stage2::revFlow(node, _, _, unbindBool(ap.toBoolNonEmpty()), unbind(config)) + flowCand(node, unbindBool(ap.toBoolNonEmpty()), unbind(config)) ) or // flow into a callable @@ -1827,6 +1839,8 @@ private module Stage4 { class ApNil = AccessPathApproxNil; + ApApprox getApprox(Ap ap) { result = ap.getFront() } + ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } bindingset[tc, tail] @@ -1845,6 +1859,10 @@ private module Stage4 { Cc ccAny() { result instanceof CallContextAny } + predicate flowCand(Node node, ApApprox apa, Configuration config) { + Stage3::revFlow(node, _, _, apa, config) + } + /* Begin: Stage 4 logic. */ /** * Holds if `node` is reachable with approximate access path `ap` from a source @@ -1856,17 +1874,17 @@ private module Stage4 { */ predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and - Stage3::revFlow(node, _, _, ap.getFront(), config) + flowCand(node, getApprox(ap), config) } private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - Stage3::revFlow(node, _, _, _, config) and + flowCand(node, _, config) and config.isSource(node) and cc = ccAny() and argAp = apNone() and ap = getApNil(node) or - Stage3::revFlow(node, _, _, _, unbind(config)) and + flowCand(node, _, unbind(config)) and ( exists(Node mid, LocalCallContext localCC | fwdFlowLocalEntry(mid, cc, argAp, ap, localCC, config) and @@ -1962,7 +1980,7 @@ private module Stage4 { Configuration config ) { storeCand2(mid, tc, node, _, config) and - Stage3::revFlow(mid, _, _, apf0, config) and + flowCand(mid, apf0, config) and apf.headUsesContent(tc) } @@ -1973,7 +1991,7 @@ private module Stage4 { exists(AccessPathFront apf | storeCand(mid, tc, node, apf0, apf, config) and stage3consCand(tc, apf0, config) and - Stage3::revFlow(node, _, _, apf, unbind(config)) + flowCand(node, apf, unbind(config)) ) } @@ -1996,7 +2014,7 @@ private module Stage4 { flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and c = p.getEnclosingCallable() and c = resolveCall(call, outercc) and - Stage3::revFlow(p, _, _, _, unbind(config)) and + flowCand(p, _, unbind(config)) and if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() @@ -2014,7 +2032,7 @@ private module Stage4 { fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and innerc = ret.getEnclosingCallable() and - Stage3::revFlow(node, _, _, _, unbind(config)) and + flowCand(node, _, unbind(config)) and ( resolveReturn(innercc, innerc, call) or From 03ef9d00ece5f93d4eeb807ee1927eb99a9b3642 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 2 Nov 2020 14:48:02 +0100 Subject: [PATCH 28/97] Dataflow: Refactor call contexts. --- .../java/dataflow/internal/DataFlowImpl.qll | 143 ++++++++++++------ 1 file changed, 97 insertions(+), 46 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 1ff4f684a23..a83c227bc45 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -764,8 +764,25 @@ private module Stage2 { class Cc = boolean; + class CcCall extends Cc { + CcCall() { this = true } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + Cc ccAny() { result = false } + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } + predicate flowCand(Node node, ApApprox apa, Configuration config) { Stage1::revFlow(node, config) and exists(apa) } @@ -828,14 +845,12 @@ private module Stage2 { ) or // flow into a callable - fwdFlowIn(_, node, _, _, ap, config) and - cc = true and + fwdFlowIn(_, node, _, cc, _, ap, config) and if parameterThroughFlowNodeCand1(node, config) then argAp = apSome(ap) else argAp = apNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, cc, argAp, ap, config) and - cc = ccAny() + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or exists(Ap argAp0 | fwdFlowOutFromArg(call, node, argAp0, ap, config) and @@ -878,11 +893,13 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, cc, argAp, ap, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -890,11 +907,14 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, cc, argAp, ap, config) and - flowOutOfCallNodeCand1(call, ret, node, allowsFieldFlow, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCallNodeCand1(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -904,7 +924,7 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, true, apSome(argAp), ap, config) + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -915,7 +935,7 @@ private module Stage2 { DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, cc, argAp, ap, config) and + fwdFlowIn(call, p, cc, _, argAp, ap, config) and parameterThroughFlowNodeCand1(p, config) ) } @@ -1288,8 +1308,25 @@ private module Stage3 { class Cc = boolean; + class CcCall extends Cc { + CcCall() { this = true } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + Cc ccAny() { result = false } + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } + predicate flowCand(Node node, ApApprox apa, Configuration config) { Stage2::revFlow(node, _, _, apa, config) } @@ -1360,16 +1397,14 @@ private module Stage3 { ) or // flow into a callable - fwdFlowIn(_, node, _, _, ap, config) and - cc = true and + fwdFlowIn(_, node, _, cc, _, ap, config) and if Stage2::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) then argAp = apSome(ap) else argAp = apNone() or // flow out of a callable exists(DataFlowCall call | - fwdFlowOut(call, node, cc, argAp, ap, config) and - cc = ccAny() + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or exists(Ap argAp0 | fwdFlowOutFromArg(call, node, argAp0, ap, config) and @@ -1411,11 +1446,13 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config ) { exists(ArgumentNode arg, boolean allowsFieldFlow | - fwdFlow(arg, cc, argAp, ap, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1423,11 +1460,14 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc cc, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - fwdFlow(ret, cc, argAp, ap, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1437,7 +1477,7 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, true, apSome(argAp), ap, config) + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1448,7 +1488,7 @@ private module Stage3 { DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { exists(ParameterNode p | - fwdFlowIn(call, p, cc, argAp, ap, config) and + fwdFlowIn(call, p, cc, _, argAp, ap, config) and Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) ) } @@ -1857,8 +1897,30 @@ private module Stage4 { class Cc = CallContext; + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + Cc ccAny() { result instanceof CallContextAny } + bindingset[call, c, outercc] + CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) + or + innercc.(CallContextCall).matchesCall(call) + } + predicate flowCand(Node node, ApApprox apa, Configuration config) { Stage3::revFlow(node, _, _, apa, config) } @@ -1934,10 +1996,7 @@ private module Stage4 { or // flow out of a callable exists(DataFlowCall call | - exists(DataFlowCallable c | - fwdFlowOut(call, node, any(CallContextNoCall innercc), c, argAp, ap, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or exists(Ap argAp0 | fwdFlowOutFromArg(call, node, argAp0, ap, config) and @@ -2009,15 +2068,11 @@ private module Stage4 { DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | + exists(ArgumentNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and flowCand(p, _, unbind(config)) and - if recordDataFlowCallSite(call, c) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2025,19 +2080,15 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, DataFlowCallable innerc, ApOption argAp, Ap ap, - Configuration config + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and + inner = ret.getEnclosingCallable() and flowCand(node, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) - or - innercc.(CallContextCall).matchesCall(call) - ) + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2047,7 +2098,7 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CallContextCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) } /** From 7a9546624113a967e1a9cab3bf6dd10e7e642354 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 3 Nov 2020 12:56:04 +0100 Subject: [PATCH 29/97] Dataflow: Remove superfluous conjuncts. --- .../java/dataflow/internal/DataFlowImpl.qll | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index a83c227bc45..c4ec2e404be 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -831,31 +831,31 @@ private module Stage2 { argAp = apNone() and ap = getApNil(node) ) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(node, tc, ap0, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + fwdFlowIn(_, node, _, cc, _, ap, config) and + if parameterThroughFlowNodeCand1(node, config) then argAp = apSome(ap) else argAp = apNone() + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - // store - exists(TypedContent tc, Ap ap0 | - fwdFlowStore(node, tc, ap0, cc, argAp, config) and - ap = apCons(tc, ap0) - ) - or - // read - exists(Ap ap0, Content c | - fwdFlowRead(ap0, c, _, node, cc, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) - ) - or - // flow into a callable - fwdFlowIn(_, node, _, cc, _, ap, config) and - if parameterThroughFlowNodeCand1(node, config) then argAp = apSome(ap) else argAp = apNone() - or - // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) } From 19a9285d00406981ebf240b0a4aa4adc5dcebe85 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 4 Nov 2020 10:10:15 +0100 Subject: [PATCH 30/97] Dataflow: Reshuffle a few conjuncts. --- .../java/dataflow/internal/DataFlowImpl.qll | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index c4ec2e404be..f80bfdb408a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -619,13 +619,13 @@ private predicate read(Node n1, Content c, Node n2, Configuration config) { pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - Stage1::revFlow(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - Stage1::revFlow(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -804,33 +804,32 @@ private module Stage2 { argAp = apNone() and ap = getApNil(node) or - flowCand(node, _, unbind(config)) and - ( - exists(Node mid | - fwdFlow(mid, cc, argAp, ap, config) and - localFlowStepNodeCand1(mid, node, config) - ) - or - exists(Node mid | - fwdFlow(mid, cc, argAp, ap, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - ap = false - ) - or - exists(Node mid | - fwdFlow(mid, _, _, ap, config) and - jumpStep(mid, node, config) and - cc = ccAny() and - argAp = apNone() - ) - or - exists(Node mid, ApNil nil | - fwdFlow(mid, _, _, nil, config) and - additionalJumpStep(mid, node, config) and - cc = ccAny() and - argAp = apNone() and - ap = getApNil(node) - ) + exists(Node mid | + fwdFlow(mid, cc, argAp, ap, config) and + localFlowStepNodeCand1(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, argAp, ap, config) and + additionalLocalFlowStepNodeCand1(mid, node, config) and + ap = false + ) + or + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store From 4b5905c5e0dccc70af8db66df3be60e00ac38f6e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 5 Nov 2020 11:05:27 +0100 Subject: [PATCH 31/97] Dataflow: Risky! Remove fwdFlowLocalEntry. This commit is a little bit risky, as it allows for some potentially bad join-orders. The best order starts with the delta and proceeds with the then functional `mid.getEnclosingCallable()` and `getLocalCallContext`. In this order `localFlowEntry` becomes superfluous. The standard order is however somewhat unwilling to choose this. If it picks `getLocalCallContext` and `getEnclosingCallable` as the first join, the result is really bad, but it appears that the existence of `localFlowEntry` at least means that it'll do `localFlowEntry`, `getEnclosingCallable`, `getLocalCallContext` in that order, which appears to be acceptable, although it isn't optimal. Without the `localFlowEntry` conjunct we end up with the worst case. We'll need to watch this particular join-ordering until we get better join-ordering directives. --- .../java/dataflow/internal/DataFlowImpl.qll | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index f80bfdb408a..6ad5a0ea0e7 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1945,33 +1945,36 @@ private module Stage4 { argAp = apNone() and ap = getApNil(node) or - flowCand(node, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - fwdFlowLocalEntry(mid, cc, argAp, ap, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + exists(Node mid, Ap ap0, LocalCallContext localCC | + fwdFlow(mid, cc, argAp, ap0, config) and + localFlowEntry(mid, config) and + localCC = getLocalCallContext(cc, mid.getEnclosingCallable()) + | + localFlowBigStep(mid, node, true, _, config, localCC) and + ap = ap0 or - exists(Node mid, ApNil nil, LocalCallContext localCC, AccessPathFront apf | - fwdFlowLocalEntry(mid, cc, argAp, nil, localCC, config) and + exists(AccessPathFront apf | localFlowBigStep(mid, node, false, apf, config, localCC) and + ap0 instanceof ApNil and apf = ap.(ApNil).getFront() ) - or - exists(Node mid | - fwdFlow(mid, _, _, ap, config) and - jumpStep(mid, node, config) and - cc = ccAny() and - argAp = apNone() - ) - or - exists(Node mid, ApNil nil | - fwdFlow(mid, _, _, nil, config) and - additionalJumpStep(mid, node, config) and - cc = ccAny() and - argAp = apNone() and - ap = getApNil(node) - ) + ) + or + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store @@ -2004,15 +2007,6 @@ private module Stage4 { ) } - pragma[nomagic] - private predicate fwdFlowLocalEntry( - Node node, Cc cc, ApOption argAp, Ap ap, LocalCallContext localCC, Configuration config - ) { - fwdFlow(node, cc, argAp, ap, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) - } - pragma[nomagic] private predicate fwdFlowStore( Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config From 0a4c680e1774ead254142deec52af7eea1a30822 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 5 Nov 2020 13:37:40 +0100 Subject: [PATCH 32/97] Dataflow: Align on localStep. --- .../java/dataflow/internal/DataFlowImpl.qll | 105 ++++++++++++------ 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 6ad5a0ea0e7..9f803afa632 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -774,6 +774,8 @@ private module Stage2 { Cc ccAny() { result = false } + class LocalCc = Unit; + bindingset[call, c, outercc] CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } @@ -787,6 +789,23 @@ private module Stage2 { Stage1::revFlow(node, config) and exists(apa) } + bindingset[node, cc, config] + LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + /* Begin: Stage 2 logic. */ /** * Holds if `node` is reachable from a source in the configuration `config`. @@ -804,15 +823,15 @@ private module Stage2 { argAp = apNone() and ap = getApNil(node) or - exists(Node mid | - fwdFlow(mid, cc, argAp, ap, config) and - localFlowStepNodeCand1(mid, node, config) - ) - or - exists(Node mid | - fwdFlow(mid, cc, argAp, ap, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - ap = false + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | @@ -970,13 +989,13 @@ private module Stage2 { ap instanceof ApNil or exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and + localStep(node, mid, true, _, config, _) and revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and - additionalLocalFlowStepNodeCand1(node, mid, config) and + localStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and ap instanceof ApNil ) @@ -1317,6 +1336,8 @@ private module Stage3 { Cc ccAny() { result = false } + class LocalCc = Unit; + bindingset[call, c, outercc] CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } @@ -1330,6 +1351,15 @@ private module Stage3 { Stage2::revFlow(node, _, _, apa, config) } + bindingset[node, cc, config] + LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + /* Begin: Stage 3 logic. */ /** * Holds if `node` is reachable with access path front `ap` from a @@ -1355,14 +1385,15 @@ private module Stage3 { argAp = apNone() and ap = getApNil(node) or - exists(Node mid | - fwdFlow(mid, cc, argAp, ap, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, ApNil nil | - fwdFlow(mid, cc, argAp, nil, config) and - localFlowBigStep(mid, node, false, ap, config, _) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | @@ -1522,13 +1553,13 @@ private module Stage3 { ap instanceof ApNil or exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and + localStep(node, mid, true, _, config, _) and revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and - localFlowBigStep(node, mid, false, _, config, _) and + localStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and ap instanceof ApNil ) @@ -1902,6 +1933,8 @@ private module Stage4 { Cc ccAny() { result instanceof CallContextAny } + class LocalCc = LocalCallContext; + bindingset[call, c, outercc] CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { c = resolveCall(call, outercc) and @@ -1924,6 +1957,18 @@ private module Stage4 { Stage3::revFlow(node, _, _, apa, config) } + bindingset[node, cc, config] + LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + /* Begin: Stage 4 logic. */ /** * Holds if `node` is reachable with approximate access path `ap` from a source @@ -1945,19 +1990,15 @@ private module Stage4 { argAp = apNone() and ap = getApNil(node) or - exists(Node mid, Ap ap0, LocalCallContext localCC | + exists(Node mid, Ap ap0, LocalCc localCc | fwdFlow(mid, cc, argAp, ap0, config) and - localFlowEntry(mid, config) and - localCC = getLocalCallContext(cc, mid.getEnclosingCallable()) + localCc = getLocalCc(mid, cc, config) | - localFlowBigStep(mid, node, true, _, config, localCC) and + localStep(mid, node, true, _, config, localCc) and ap = ap0 or - exists(AccessPathFront apf | - localFlowBigStep(mid, node, false, apf, config, localCC) and - ap0 instanceof ApNil and - apf = ap.(ApNil).getFront() - ) + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | @@ -2137,13 +2178,13 @@ private module Stage4 { ap instanceof ApNil or exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and + localStep(node, mid, true, _, config, _) and revFlow(mid, toReturn, returnAp, ap, config) ) or exists(Node mid, ApNil nil | fwdFlow(node, _, _, ap, config) and - localFlowBigStep(node, mid, false, _, config, _) and + localStep(node, mid, false, _, config, _) and revFlow(mid, toReturn, returnAp, nil, config) and ap instanceof ApNil ) From aa28fdb83d14f0aa6f632edf3c06935e68d46ff4 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 5 Nov 2020 13:44:43 +0100 Subject: [PATCH 33/97] Dataflow: Align some qldoc. --- .../java/dataflow/internal/DataFlowImpl.qll | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 9f803afa632..b31e3838d36 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -808,13 +808,12 @@ private module Stage2 { /* Begin: Stage 2 logic. */ /** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `ap` records whether the tracked value is stored into a - * field of `node`. + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. * - * The Boolean `cc` records whether the node is reached through an - * argument in a call, and if so, `argAp` records whether the tracked - * value was stored into a field of the argument. + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. */ private predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and @@ -964,13 +963,12 @@ private module Stage2 { } /** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `ap` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. + * Holds if `node` with access path `ap` 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, and if so, `returnAp` - * records whether a field must be read from the returned value. + * The Boolean `toReturn` records whether 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] predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { @@ -1362,12 +1360,12 @@ private module Stage3 { /* Begin: Stage 3 logic. */ /** - * Holds if `node` is reachable with access path front `ap` from a - * source in the configuration `config`. + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. * - * The Boolean `cc` records whether the node is reached through an - * argument in a call, and if so, `argAp` records the front of the - * access path of that argument. + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. */ pragma[nomagic] predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { @@ -1529,12 +1527,12 @@ private module Stage3 { } /** - * Holds if `node` with access path front `ap` is part of a path from a - * source to a sink in the configuration `config`. + * Holds if `node` with access path `ap` 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, and if so, `returnAp` - * records the front of the access path of the returned value. + * The Boolean `toReturn` records whether 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] predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { @@ -1971,12 +1969,12 @@ private module Stage4 { /* Begin: Stage 4 logic. */ /** - * Holds if `node` is reachable with approximate access path `ap` from a source - * in the configuration `config`. + * 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, `argAp` records the approximate access path - * of that argument. + * argument in a call, and if so, `argAp` records the access path of that + * argument. */ predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and @@ -2154,12 +2152,12 @@ private module Stage4 { } /** - * Holds if `node` with approximate access path `ap` is part of a path from a - * source to a sink in the configuration `config`. + * Holds if `node` with access path `ap` 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, and if so, `returnAp` - * records the approximate access path of the returned value. + * The Boolean `toReturn` records whether 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] predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { From 12fe38bcb6d4818bc1c749f523a66061dec2c04f Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 6 Nov 2020 14:09:17 +0100 Subject: [PATCH 34/97] Dataflow: Reorder, rename, and add columns to store-flow. --- .../java/dataflow/internal/DataFlowImpl.qll | 89 +++++++++---------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index b31e3838d36..6a68ee9ab43 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -852,7 +852,7 @@ private module Stage2 { or // store exists(TypedContent tc, Ap ap0 | - fwdFlowStore(node, tc, ap0, cc, argAp, config) and + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and ap = apCons(tc, ap0) ) or @@ -879,12 +879,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config ) { - exists(Node mid | - fwdFlow(mid, cc, argAp, ap0, config) and - storeCand1(mid, tc, node, config) - ) + fwdFlow(node1, cc, argAp, ap1, config) and + storeCand1(node1, tc, node2, config) } /** @@ -893,7 +891,7 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { exists(TypedContent tc | - fwdFlowStore(_, tc, tail, _, _, config) and + fwdFlowStore(_, tail, tc, _, _, _, config) and tc.getContent() = c and cons = apCons(tc, tail) ) @@ -1016,7 +1014,7 @@ private module Stage2 { or // store exists(Ap ap0, Content c | - revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and revFlowConsCand(ap0, c, ap, config) ) or @@ -1047,15 +1045,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowStore( - Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config ) { - exists(Node mid, TypedContent tc | - revFlow(mid, toReturn, returnAp, ap0, config) and - storeCand1(node, tc, mid, config) and - tc.getContent() = c and - ap0 = true and - fwdFlow(node, _, _, ap, unbind(config)) - ) + revFlow(mid, toReturn, returnAp, ap0, config) and + storeCand1(node, tc, mid, config) and + tc.getContent() = c and + ap0 = true and + fwdFlow(node, _, _, ap, unbind(config)) } /** @@ -1075,7 +1072,7 @@ private module Stage2 { pragma[nomagic] private predicate revFlowIsStored(Content c, Ap ap, Configuration conf) { exists(Node node | - revFlowStore(_, c, node, _, _, ap, conf) and + revFlowStore(_, c, ap, node, _, _, _, _, conf) and revFlow(node, _, _, ap, conf) ) } @@ -1413,7 +1410,7 @@ private module Stage3 { or // store exists(TypedContent tc, Ap ap0 | - fwdFlowStore(node, tc, ap0, cc, argAp, config) and + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and ap = apCons(tc, ap0) ) or @@ -1443,21 +1440,21 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config ) { - exists(Node mid, DataFlowType contentType | - fwdFlow(mid, cc, argAp, ap0, config) and - storeCand2(mid, tc, node, contentType, config) and + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + storeCand2(node1, tc, node2, contentType, config) and // We need to typecheck stores here, since reverse flow through a getter // might have a different type here compared to inside the getter. - compatibleTypes(ap0.getType(), contentType) + compatibleTypes(ap1.getType(), contentType) ) } pragma[nomagic] private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { exists(TypedContent tc | - fwdFlowStore(_, tc, tail, _, _, config) and + fwdFlowStore(_, tail, tc, _, _, _, config) and tc.getContent() = c and cons = apCons(tc, tail) ) @@ -1580,7 +1577,7 @@ private module Stage3 { or // store exists(Ap ap0, Content c | - revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and revFlowConsCand(ap0, c, ap, config) ) or @@ -1616,15 +1613,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowStore( - Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config ) { - exists(Node mid, TypedContent tc | - revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and - fwdFlow(node, _, _, ap, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - ap0 = TFrontHead(tc) and - tc.getContent() = c - ) + revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and + fwdFlow(node, _, _, ap, config) and + storeCand2(node, tc, mid, _, unbind(config)) and + ap0 = TFrontHead(tc) and + tc.getContent() = c } pragma[nomagic] @@ -2018,7 +2014,7 @@ private module Stage4 { or // store exists(TypedContent tc, Ap ap0 | - fwdFlowStore(node, tc, ap0, cc, argAp, config) and + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and ap = apCons(tc, ap0) ) or @@ -2048,18 +2044,16 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowStore( - Node node, TypedContent tc, Ap ap0, Cc cc, ApOption argAp, Configuration config + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config ) { - exists(Node mid | - fwdFlow(mid, cc, argAp, ap0, config) and - fwdFlowStore0(mid, tc, node, ap0.getFront(), config) - ) + fwdFlow(node1, cc, argAp, ap1, config) and + fwdFlowStore0(node1, tc, node2, ap1.getFront(), config) } pragma[nomagic] private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { exists(TypedContent tc | - fwdFlowStore(_, tc, tail, _, _, config) and + fwdFlowStore(_, tail, tc, _, _, _, config) and tc.getContent() = c and cons = apCons(tc, tail) ) @@ -2205,7 +2199,7 @@ private module Stage4 { or // store exists(Ap ap0, Content c | - revFlowStore(ap0, c, node, toReturn, returnAp, ap, config) and + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and revFlowConsCand(ap0, c, ap, config) ) or @@ -2239,19 +2233,18 @@ private module Stage4 { Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config ) { storeCand2(node1, tc, node2, _, config) and - fwdFlowStore(node2, tc, ap, _, _, config) and + fwdFlowStore(_, ap, tc, node2, _, _, config) and ap0 = push(tc, ap) } pragma[nomagic] private predicate revFlowStore( - Ap ap0, Content c, Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config ) { - exists(Node mid, TypedContent tc | - revFlow(mid, toReturn, returnAp, ap0, config) and - storeFlowFwd(node, tc, mid, ap, ap0, config) and - tc.getContent() = c - ) + revFlow(mid, toReturn, returnAp, ap0, config) and + storeFlowFwd(node, tc, mid, ap, ap0, config) and + tc.getContent() = c } pragma[nomagic] From b6f1ab642943ddaf3fa07993ff997b1e60301923 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 6 Nov 2020 14:33:10 +0100 Subject: [PATCH 35/97] Dataflow: Refactor step relation in revFlowStore. --- .../java/dataflow/internal/DataFlowImpl.qll | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 6a68ee9ab43..cac53b8216f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -955,6 +955,15 @@ private module Stage2 { ) } + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { fwdFlowRead(ap1, c, n1, n2, _, _, config) and fwdFlowConsCand(ap1, c, ap2, config) @@ -1049,10 +1058,8 @@ private module Stage2 { ApOption returnAp, Configuration config ) { revFlow(mid, toReturn, returnAp, ap0, config) and - storeCand1(node, tc, mid, config) and - tc.getContent() = c and - ap0 = true and - fwdFlow(node, _, _, ap, unbind(config)) + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c } /** @@ -1518,6 +1525,15 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { fwdFlowRead(ap1, c, n1, n2, _, _, config) and fwdFlowConsCand(ap1, c, ap2, config) @@ -1616,10 +1632,8 @@ private module Stage3 { Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, ApOption returnAp, Configuration config ) { - revFlow(mid, toReturn, returnAp, ap0, unbind(config)) and - fwdFlow(node, _, _, ap, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - ap0 = TFrontHead(tc) and + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and tc.getContent() = c } @@ -2140,6 +2154,15 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { fwdFlowRead(ap1, c, n1, n2, _, _, config) and fwdFlowConsCand(ap1, c, ap2, config) @@ -2228,22 +2251,13 @@ private module Stage4 { else returnAp = apNone() } - pragma[nomagic] - private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, Ap ap, Ap ap0, Configuration config - ) { - storeCand2(node1, tc, node2, _, config) and - fwdFlowStore(_, ap, tc, node2, _, _, config) and - ap0 = push(tc, ap) - } - pragma[nomagic] private predicate revFlowStore( Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, ApOption returnAp, Configuration config ) { revFlow(mid, toReturn, returnAp, ap0, config) and - storeFlowFwd(node, tc, mid, ap, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and tc.getContent() = c } From c5a2c261dc843bdb46b40dcbf20e94ecfa0c8bd8 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Nov 2020 14:56:08 +0100 Subject: [PATCH 36/97] Dataflow: Refactor forward store step relation. --- .../java/dataflow/internal/DataFlowImpl.qll | 80 +++++++++---------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index cac53b8216f..38e094c988f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1142,6 +1142,17 @@ private module Stage2 { ) } + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } /* End: Stage 2 logic. */ } @@ -1292,16 +1303,6 @@ private predicate readCand2(Node node1, Content c, Node node2, Configuration con Stage2::revFlowIsReadAndStored(c, unbind(config)) } -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - Stage2::revFlow(node1, config) and - Stage2::revFlow(node2, _, _, true, unbind(config)) and - Stage2::revFlowIsReadAndStored(tc.getContent(), unbind(config)) -} - private module Stage3 { class ApApprox = Stage2::Ap; @@ -1451,7 +1452,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - storeCand2(node1, tc, node2, contentType, config) and + Stage2::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and // We need to typecheck stores here, since reverse flow through a getter // might have a different type here compared to inside the getter. compatibleTypes(ap1.getType(), contentType) @@ -1691,6 +1692,17 @@ private module Stage3 { ) } + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { exists(Ap ap | revFlow(node2, _, _, ap, config) and @@ -2060,8 +2072,10 @@ private module Stage4 { private predicate fwdFlowStore( Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config ) { - fwdFlow(node1, cc, argAp, ap1, config) and - fwdFlowStore0(node1, tc, node2, ap1.getFront(), config) + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + Stage3::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) + ) } pragma[nomagic] @@ -2073,27 +2087,6 @@ private module Stage4 { ) } - pragma[nomagic] - private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config - ) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, apf0, config) and - apf.headUsesContent(tc) - } - - pragma[noinline] - private predicate fwdFlowStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, Configuration config - ) { - exists(AccessPathFront apf | - storeCand(mid, tc, node, apf0, apf, config) and - stage3consCand(tc, apf0, config) and - flowCand(node, apf, unbind(config)) - ) - } - pragma[nomagic] private predicate fwdFlowRead( Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config @@ -2316,6 +2309,17 @@ private module Stage4 { ) } + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } /* End: Stage 4 logic. */ } @@ -2929,18 +2933,12 @@ private predicate pathReadStep( cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - Stage4::revFlow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } From 3e18e02d2cddc7b936f427535dccf3e169d3d106 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 10 Nov 2020 12:16:42 +0100 Subject: [PATCH 37/97] Dataflow: Refactor step predicate in fwdFlowRead. --- .../java/dataflow/internal/DataFlowImpl.qll | 72 ++++++------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 38e094c988f..e1d0736c776 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1073,29 +1073,6 @@ private module Stage2 { ) } - /** - * Holds if `c` is the target of a store in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowIsStored(Content c, Ap ap, Configuration conf) { - exists(Node node | - revFlowStore(_, c, ap, node, _, _, _, _, conf) and - revFlow(node, _, _, ap, conf) - ) - } - - /** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `revFlow`. - */ - pragma[noinline] - predicate revFlowIsReadAndStored(Content c, Configuration conf) { - exists(Ap ap | - revFlowIsStored(c, ap, conf) and - revFlowConsCand(_, c, ap, conf) - ) - } - pragma[nomagic] private predicate revFlowOut( DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, @@ -1153,6 +1130,14 @@ private module Stage2 { ) } + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } /* End: Stage 2 logic. */ } @@ -1295,14 +1280,6 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - Stage2::revFlow(node1, _, _, true, unbind(config)) and - Stage2::revFlow(node2, config) and - Stage2::revFlowIsReadAndStored(c, unbind(config)) -} - private module Stage3 { class ApApprox = Stage2::Ap; @@ -1473,7 +1450,7 @@ private module Stage3 { Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { fwdFlow(node1, cc, argAp, ap, config) and - readCand2(node1, c, node2, config) and + Stage2::readStepCand(node1, c, node2, config) and getHeadContent(ap) = c } @@ -1621,13 +1598,6 @@ private module Stage3 { if fwdFlow(node, true, _, ap, config) then returnAp = apSome(ap) else returnAp = apNone() } - // TODO: remove - pragma[nomagic] - predicate readCandFwd(Node node1, TypedContent tc, Ap ap, Node node2, Configuration config) { - fwdFlowRead(ap, _, node1, node2, _, _, config) and - ap.headUsesContent(tc) - } - pragma[nomagic] private predicate revFlowStore( Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, @@ -1704,9 +1674,10 @@ private module Stage3 { } predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { - exists(Ap ap | - revFlow(node2, _, _, ap, config) and - readStepFwd(node1, _, c, node2, ap, config) + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) ) } /* End: Stage 3 logic. */ @@ -2320,6 +2291,14 @@ private module Stage4 { ) } + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } /* End: Stage 4 logic. */ } @@ -2918,18 +2897,13 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - Stage3::readCandFwd(node1, tc, _, node2, config) and - Stage4::revFlow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } From 5a1c0e9ec4a5bf2083c33a95d076d0eae223deca Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 10 Nov 2020 14:43:42 +0100 Subject: [PATCH 38/97] Dataflow: Get rid of early filter. This constructs a few more tuples in Stage3::fwdFlow0, which are then filtered in Stage3::fwdFlow. This is cleaner and appears faster. --- .../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index e1d0736c776..4107f992edb 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1402,8 +1402,7 @@ private module Stage3 { // read exists(Ap ap0, Content c | fwdFlowRead(ap0, c, _, node, cc, argAp, config) and - fwdFlowConsCand(ap0, c, ap, config) and - flowCand(node, unbindBool(ap.toBoolNonEmpty()), unbind(config)) + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable From e4fb41507b36789b15a5aa4f16757d8b396eb6f9 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 11 Nov 2020 11:12:15 +0100 Subject: [PATCH 39/97] Dataflow: Reshuffle some predicates. --- .../java/dataflow/internal/DataFlowImpl.qll | 108 +++++++++++------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 4107f992edb..e00223710ea 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -502,7 +502,7 @@ private module Stage1 { * Holds if `c` is the target of both a read and a store in the flow covered * by `revFlow`. */ - predicate revFlowIsReadAndStored(Content c, Configuration conf) { + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { revFlowIsRead(c, conf) and revFlowStore(c, _, _, conf) } @@ -557,8 +557,32 @@ private module Stage1 { ) } + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + pragma[nomagic] predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } /* End: Stage 1 logic. */ } @@ -600,23 +624,6 @@ private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration c ) } -pragma[nomagic] -private predicate storeCand1(Node n1, TypedContent tc, Node n2, Configuration config) { - exists(Content c | - Stage1::revFlowIsReadAndStored(c, config) and - Stage1::revFlow(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - Stage1::revFlowIsReadAndStored(c, config) and - Stage1::revFlow(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { Stage1::revFlow(node2, config) and @@ -740,7 +747,9 @@ private predicate flowIntoCallNodeCand1( } private module Stage2 { - class ApApprox = Stage1::Ap; + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; class Ap = boolean; @@ -748,6 +757,9 @@ private module Stage2 { ApNil() { this = false } } + bindingset[result, ap] + ApApprox getApprox(Ap ap) { any() } + ApNil getApNil(Node node) { any() } bindingset[tc, tail] @@ -785,10 +797,6 @@ private module Stage2 { bindingset[innercc, inner, call] predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } - predicate flowCand(Node node, ApApprox apa, Configuration config) { - Stage1::revFlow(node, config) and exists(apa) - } - bindingset[node, cc, config] LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } @@ -807,6 +815,10 @@ private module Stage2 { } /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -881,8 +893,10 @@ private module Stage2 { private predicate fwdFlowStore( Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config ) { - fwdFlow(node1, cc, argAp, ap1, config) and - storeCand1(node1, tc, node2, config) + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) + ) } /** @@ -902,7 +916,7 @@ private module Stage2 { Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { fwdFlow(node1, cc, argAp, ap, config) and - read(node1, c, node2, config) and + PrevStage::readStepCand(node1, c, node2, config) and getHeadContent(ap) = c } @@ -1281,7 +1295,9 @@ private module LocalFlowBigStep { private import LocalFlowBigStep private module Stage3 { - class ApApprox = Stage2::Ap; + module PrevStage = Stage2; + + class ApApprox = PrevStage::Ap; class Ap = AccessPathFront; @@ -1327,10 +1343,6 @@ private module Stage3 { bindingset[innercc, inner, call] predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } - predicate flowCand(Node node, ApApprox apa, Configuration config) { - Stage2::revFlow(node, _, _, apa, config) - } - bindingset[node, cc, config] LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } @@ -1341,6 +1353,10 @@ private module Stage3 { } /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1407,7 +1423,7 @@ private module Stage3 { or // flow into a callable fwdFlowIn(_, node, _, cc, _, ap, config) and - if Stage2::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) + if PrevStage::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) then argAp = apSome(ap) else argAp = apNone() or @@ -1428,7 +1444,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - Stage2::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and // We need to typecheck stores here, since reverse flow through a getter // might have a different type here compared to inside the getter. compatibleTypes(ap1.getType(), contentType) @@ -1449,7 +1465,7 @@ private module Stage3 { Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { fwdFlow(node1, cc, argAp, ap, config) and - Stage2::readStepCand(node1, c, node2, config) and + PrevStage::readStepCand(node1, c, node2, config) and getHeadContent(ap) = c } @@ -1498,7 +1514,7 @@ private module Stage3 { ) { exists(ParameterNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and - Stage2::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) + PrevStage::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) ) } @@ -1893,7 +1909,9 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } private module Stage4 { - class ApApprox = Stage3::Ap; + module PrevStage = Stage3; + + class ApApprox = PrevStage::Ap; class Ap = AccessPathApprox; @@ -1943,10 +1961,6 @@ private module Stage4 { innercc.(CallContextCall).matchesCall(call) } - predicate flowCand(Node node, ApApprox apa, Configuration config) { - Stage3::revFlow(node, _, _, apa, config) - } - bindingset[node, cc, config] LocalCc getLocalCc(Node node, Cc cc, Configuration config) { localFlowEntry(node, config) and @@ -1960,6 +1974,10 @@ private module Stage4 { } /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2024,7 +2042,9 @@ private module Stage4 { exists(ApApprox apa | fwdFlowIn(_, node, _, cc, _, ap, config) and apa = ap.getFront() and - if Stage3::revFlow(node, true, _, apa, config) then argAp = apSome(ap) else argAp = apNone() + if PrevStage::revFlow(node, true, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() ) or // flow out of a callable @@ -2044,7 +2064,7 @@ private module Stage4 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - Stage3::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) ) } @@ -2062,7 +2082,7 @@ private module Stage4 { Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config ) { fwdFlow(node1, cc, argAp, ap, config) and - Stage3::readStepCand(node1, c, node2, config) and + PrevStage::readStepCand(node1, c, node2, config) and getHeadContent(ap) = c } @@ -2113,7 +2133,7 @@ private module Stage4 { ) { exists(ParameterNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and - Stage3::revFlow(p, true, TAccessPathFrontSome(_), ap.getFront(), config) + PrevStage::revFlow(p, true, TAccessPathFrontSome(_), ap.getFront(), config) ) } From 8b5e452728a0e3b144442b379adeb75446eb9b94 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 11 Nov 2020 13:17:49 +0100 Subject: [PATCH 40/97] Dataflow: Improve cons-cand relation. Post-recursion we can filter the forward cons-candidates to only include those that met a read step, and similarly restrict the reverse flow cons-candidates to those that met a store step. --- .../java/dataflow/internal/DataFlowImpl.qll | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index e00223710ea..5e3c2ce8b18 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1153,6 +1153,14 @@ private module Stage2 { } predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } /* End: Stage 2 logic. */ } @@ -1624,7 +1632,7 @@ private module Stage3 { } pragma[nomagic] - predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { exists(Node mid | revFlow(mid, _, _, tail, config) and readStepFwd(_, cons, c, mid, tail, config) @@ -1695,14 +1703,15 @@ private module Stage3 { revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) ) } - /* End: Stage 3 logic. */ -} -private predicate stage3consCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(AccessPathFront apf0 | - Stage3::revFlowConsCand(apf0, _, apf, config) and - apf0.getHead() = tc - ) + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + /* End: Stage 3 logic. */ } /** @@ -1722,7 +1731,7 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | stage3consCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) @@ -1738,11 +1747,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - stage3consCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - stage3consCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1872,7 +1881,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | stage3consCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1883,7 +1892,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - stage3consCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -2245,7 +2254,7 @@ private module Stage4 { } pragma[nomagic] - predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { exists(Node mid | revFlow(mid, _, _, tail, config) and readStepFwd(_, cons, c, mid, tail, config) @@ -2319,14 +2328,15 @@ private module Stage4 { } predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } - /* End: Stage 4 logic. */ -} -private predicate stage4consCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(AccessPathApprox apa0 | - Stage4::revFlowConsCand(apa0, _, apa, config) and - apa0.getHead() = tc - ) + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] @@ -2405,7 +2415,7 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - stage4consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) @@ -2433,7 +2443,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - stage4consCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2651,7 +2661,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - stage4consCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2682,7 +2692,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - stage4consCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } From af54afa24b544b828ccb5991f2e513fe704c0de7 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 11 Nov 2020 13:22:41 +0100 Subject: [PATCH 41/97] Dataflow: Add stage statistics. --- .../java/dataflow/internal/DataFlowImpl.qll | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 5e3c2ce8b18..f7d173b705e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -583,6 +583,20 @@ private module Stage1 { predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowIsStored(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowIsRead(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } /* End: Stage 1 logic. */ } @@ -1161,6 +1175,20 @@ private module Stage2 { predicate consCand(TypedContent tc, Ap ap, Configuration config) { storeStepCand(_, ap, tc, _, _, config) } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } /* End: Stage 2 logic. */ } @@ -1711,6 +1739,20 @@ private module Stage3 { predicate consCand(TypedContent tc, Ap ap, Configuration config) { storeStepCand(_, ap, tc, _, _, config) } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } /* End: Stage 3 logic. */ } @@ -2336,6 +2378,20 @@ private module Stage4 { predicate consCand(TypedContent tc, Ap ap, Configuration config) { storeStepCand(_, ap, tc, _, _, config) } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } /* End: Stage 4 logic. */ } @@ -3122,6 +3178,48 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0)) and + fields = + count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getFront().headUsesContent(f0))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0 and reach(pn))) and + fields = + count(TypedContent f0 | + exists(PathNodeMid pn | pn.getAp().getFront().headUsesContent(f0) and reach(pn)) + ) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + tuples = count(PathNode pn | reach(pn)) +} + +predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | From 15bf1b10269d27abffd4df9a4fbae6567c27e089 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 11 Nov 2020 15:08:32 +0100 Subject: [PATCH 42/97] Dataflow: Rename some stage 1 predicates. --- .../java/dataflow/internal/DataFlowImpl.qll | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index f7d173b705e..413b9b177ff 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -326,7 +326,7 @@ private module Stage1 { // read exists(Content c | fwdFlowRead(c, node, cc, config) and - fwdFlowIsStored(c, config) and + fwdFlowConsCand(c, config) and not inBarrier(node, config) ) or @@ -362,7 +362,7 @@ private module Stage1 { * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. */ pragma[nomagic] - private predicate fwdFlowIsStored(Content c, Configuration config) { + private predicate fwdFlowConsCand(Content c, Configuration config) { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and @@ -413,12 +413,12 @@ private module Stage1 { */ pragma[nomagic] predicate revFlow(Node node, boolean toReturn, Configuration config) { - revFlow_0(node, toReturn, config) and + revFlow0(node, toReturn, config) and fwdFlow(node, config) } pragma[nomagic] - private predicate revFlow_0(Node node, boolean toReturn, Configuration config) { + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { fwdFlow(node, config) and config.isSink(node) and toReturn = false @@ -448,13 +448,13 @@ private module Stage1 { // store exists(Content c | revFlowStore(c, node, toReturn, config) and - revFlowIsRead(c, config) + revFlowConsCand(c, config) ) or // read exists(Node mid, Content c | read(node, c, mid) and - fwdFlowIsStored(c, unbind(config)) and + fwdFlowConsCand(c, unbind(config)) and revFlow(mid, toReturn, config) ) or @@ -479,11 +479,11 @@ private module Stage1 { * Holds if `c` is the target of a read in the flow covered by `revFlow`. */ pragma[nomagic] - private predicate revFlowIsRead(Content c, Configuration config) { + private predicate revFlowConsCand(Content c, Configuration config) { exists(Node mid, Node node | fwdFlow(node, unbind(config)) and read(node, c, mid) and - fwdFlowIsStored(c, unbind(config)) and + fwdFlowConsCand(c, unbind(config)) and revFlow(mid, _, config) ) } @@ -492,7 +492,7 @@ private module Stage1 { private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { exists(Node mid, TypedContent tc | revFlow(mid, toReturn, config) and - fwdFlowIsStored(c, unbind(config)) and + fwdFlowConsCand(c, unbind(config)) and store(node, tc, mid, _) and c = tc.getContent() ) @@ -503,7 +503,7 @@ private module Stage1 { * by `revFlow`. */ private predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowIsRead(c, conf) and + revFlowConsCand(c, conf) and revFlowStore(c, _, _, conf) } @@ -587,13 +587,13 @@ private module Stage1 { predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowIsStored(f0, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and conscand = -1 and tuples = count(Node n, boolean b | fwdFlow(n, b, config)) or fwd = false and nodes = count(Node node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowIsRead(f0, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and conscand = -1 and tuples = count(Node n, boolean b | revFlow(n, b, config)) } From 786edbf045859a7e727d88cbfc74236cba36f308 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 12 Nov 2020 15:30:06 +0100 Subject: [PATCH 43/97] Dataflow: Align on parameterMayFlowThrough. This actually provides a decent pruning improvement in stages 3 and 4. --- .../java/dataflow/internal/DataFlowImpl.qll | 204 +++++++++++------- 1 file changed, 131 insertions(+), 73 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 413b9b177ff..e0ee73b6d0b 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -584,6 +584,43 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -603,41 +640,6 @@ private module Stage1 { bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -private predicate throughFlowNodeCand1(Node node, Configuration config) { - Stage1::revFlow(node, true, config) and - Stage1::fwdFlow(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { Stage1::revFlow(node2, config) and @@ -889,8 +891,13 @@ private module Stage2 { ) or // flow into a callable - fwdFlowIn(_, node, _, cc, _, ap, config) and - if parameterThroughFlowNodeCand1(node, config) then argAp = apSome(ap) else argAp = apNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | @@ -979,7 +986,7 @@ private module Stage2 { ) { exists(ParameterNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and - parameterThroughFlowNodeCand1(p, config) + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) } @@ -1075,7 +1082,7 @@ private module Stage2 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, apSome(_), unbindBool(ap), config) + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) then returnAp = apSome(ap) else returnAp = apNone() } @@ -1176,6 +1183,27 @@ private module Stage2 { storeStepCand(_, ap, tc, _, _, config) } + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1339,8 +1367,7 @@ private module Stage3 { class ApNil = AccessPathFrontNil; - bindingset[result, ap] - ApApprox getApprox(Ap ap) { result = unbindBool(ap.toBoolNonEmpty()) } + ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } @@ -1404,7 +1431,7 @@ private module Stage3 { pragma[nomagic] predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and - flowCand(node, getApprox(ap), config) and + flowCand(node, unbindBool(getApprox(ap)), config) and not ap.isClearedAt(node) and if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() } @@ -1458,10 +1485,13 @@ private module Stage3 { ) or // flow into a callable - fwdFlowIn(_, node, _, cc, _, ap, config) and - if PrevStage::revFlow(node, true, _, unbindBool(ap.toBoolNonEmpty()), config) - then argAp = apSome(ap) - else argAp = apNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | @@ -1550,7 +1580,7 @@ private module Stage3 { ) { exists(ParameterNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and - PrevStage::revFlow(p, true, TBooleanSome(_), unbindBool(ap.toBoolNonEmpty()), config) + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) ) } @@ -1646,7 +1676,9 @@ private module Stage3 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, true, _, ap, config) then returnAp = apSome(ap) else returnAp = apNone() + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() } pragma[nomagic] @@ -1740,6 +1772,27 @@ private module Stage3 { storeStepCand(_, ap, tc, _, _, config) } + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2092,8 +2145,8 @@ private module Stage4 { // flow into a callable exists(ApApprox apa | fwdFlowIn(_, node, _, cc, _, ap, config) and - apa = ap.getFront() and - if PrevStage::revFlow(node, true, _, apa, config) + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) then argAp = apSome(ap) else argAp = apNone() ) @@ -2184,7 +2237,7 @@ private module Stage4 { ) { exists(ParameterNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and - PrevStage::revFlow(p, true, TAccessPathFrontSome(_), ap.getFront(), config) + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) } @@ -2280,7 +2333,7 @@ private module Stage4 { // flow out of a callable revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if fwdFlow(node, any(CallContextCall ccc), apSome(_), ap, config) + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) then returnAp = apSome(ap) else returnAp = apNone() } @@ -2343,7 +2396,7 @@ private module Stage4 { private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, CallContextCall ccc | + exists(ReturnNodeExt ret, CcCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and fwdFlow(ret, ccc, apSome(_), ap, config) and ccc.matchesCall(call) @@ -2379,6 +2432,27 @@ private module Stage4 { storeStepCand(_, ap, tc, _, _, config) } + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2398,27 +2472,9 @@ private module Stage4 { bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - Stage4::revFlow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - Stage4::revFlow(ret, true, TAccessPathApproxSome(_), apa0, config) and - Stage4::fwdFlow(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and Stage4::revFlow(n, true, _, apa0, config) and Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c @@ -2427,7 +2483,9 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through From 6e6e5d641442beeb2a6ac8712ddab5916a4c9d05 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 12:53:58 +0100 Subject: [PATCH 44/97] Dataflow: Renamings. --- .../java/dataflow/internal/DataFlowImpl.qll | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index e0ee73b6d0b..581dc798767 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -830,6 +830,10 @@ private module Stage2 { exists(lcc) } + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + /* Begin: Stage 2 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -948,7 +952,7 @@ private module Stage2 { ) { exists(ArgumentNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true @@ -961,7 +965,7 @@ private module Stage2 { ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCallNodeCand1(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1115,7 +1119,7 @@ private module Stage2 { ) { exists(Node out, boolean allowsFieldFlow | revFlow(out, toReturn, returnAp, ap, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) + flowOutOfCall(call, ret, out, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1128,7 +1132,7 @@ private module Stage2 { ) { exists(ParameterNode p, boolean allowsFieldFlow | revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) + flowIntoCall(call, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1148,9 +1152,9 @@ private module Stage2 { private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret | + exists(ReturnNodeExt ret, CcCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, true, apSome(_), ap, config) + fwdFlow(ret, ccc, apSome(_), ap, config) ) } @@ -1415,6 +1419,10 @@ private module Stage3 { localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) } + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + /* Begin: Stage 3 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -1542,7 +1550,7 @@ private module Stage3 { ) { exists(ArgumentNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true @@ -1555,7 +1563,7 @@ private module Stage3 { ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1706,7 +1714,7 @@ private module Stage3 { ) { exists(Node out, boolean allowsFieldFlow | revFlow(out, toReturn, returnAp, ap, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) + flowOutOfCall(call, ret, out, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1719,7 +1727,7 @@ private module Stage3 { ) { exists(ParameterNode p, boolean allowsFieldFlow | revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + flowIntoCall(call, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1739,9 +1747,9 @@ private module Stage3 { private predicate revFlowIsReturned( DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret | + exists(ReturnNodeExt ret, CcCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, true, apSome(_), ap, config) + fwdFlow(ret, ccc, apSome(_), ap, config) ) } @@ -2077,6 +2085,26 @@ private module Stage4 { localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) } + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + /* Begin: Stage 4 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -2197,8 +2225,7 @@ private module Stage4 { ) { exists(ArgumentNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - flowCand(p, _, unbind(config)) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) | ap instanceof ApNil or allowsFieldFlow = true @@ -2211,9 +2238,8 @@ private module Stage4 { ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and - flowCand(node, _, unbind(config)) and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) | @@ -2363,7 +2389,7 @@ private module Stage4 { ) { exists(Node out, boolean allowsFieldFlow | revFlow(out, toReturn, returnAp, ap, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) + flowOutOfCall(call, ret, out, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2376,7 +2402,7 @@ private module Stage4 { ) { exists(ParameterNode p, boolean allowsFieldFlow | revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) + flowIntoCall(call, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) From aa66b9bb48fdce49a4ea54aefce1a04c46cf3b62 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 14:04:07 +0100 Subject: [PATCH 45/97] Dataflow: Align more predicates. --- .../java/dataflow/internal/DataFlowImpl.qll | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 581dc798767..0bd01208ee3 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -794,6 +794,9 @@ private module Stage2 { class CcCall extends Cc { CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } } class CcNoCall extends Cc { @@ -834,6 +837,9 @@ private module Stage2 { private predicate flowIntoCall = flowIntoCallNodeCand1/5; + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + /* Begin: Stage 2 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -847,7 +853,8 @@ private module Stage2 { * argument in a call, and if so, `argAp` records the access path of that * argument. */ - private predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and cc = ccAny() and @@ -920,7 +927,8 @@ private module Stage2 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) ) } @@ -1154,7 +1162,8 @@ private module Stage2 { ) { exists(ReturnNodeExt ret, CcCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, ccc, apSome(_), ap, config) + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) ) } @@ -1391,6 +1400,9 @@ private module Stage3 { class CcCall extends Cc { CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } } class CcNoCall extends Cc { @@ -1423,6 +1435,19 @@ private module Stage3 { private predicate flowIntoCall = flowIntoCallNodeCand2/5; + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + /* Begin: Stage 3 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -1440,13 +1465,12 @@ private module Stage3 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and flowCand(node, unbindBool(getApprox(ap)), config) and - not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + filter(node, ap) } pragma[nomagic] private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - flowCand(node, false, config) and + flowCand(node, _, config) and config.isSource(node) and cc = ccAny() and argAp = apNone() and @@ -1519,9 +1543,7 @@ private module Stage3 { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and - // We need to typecheck stores here, since reverse flow through a getter - // might have a different type here compared to inside the getter. - compatibleTypes(ap1.getType(), contentType) + typecheckStore(ap1, contentType) ) } @@ -1749,7 +1771,8 @@ private module Stage3 { ) { exists(ReturnNodeExt ret, CcCall ccc | revFlowOut(call, ret, toReturn, returnAp, ap, config) and - fwdFlow(ret, ccc, apSome(_), ap, config) + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) ) } @@ -1772,6 +1795,8 @@ private module Stage3 { ) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { storeStepFwd(_, ap, tc, _, _, config) } @@ -2105,6 +2130,12 @@ private module Stage4 { PrevStage::revFlow(node1, _, _, _, unbind(config)) } + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + /* Begin: Stage 4 logic. */ private predicate flowCand(Node node, ApApprox apa, Configuration config) { PrevStage::revFlow(node, _, _, apa, config) @@ -2118,11 +2149,14 @@ private module Stage4 { * argument in a call, and if so, `argAp` records the access path of that * argument. */ + pragma[nomagic] predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { fwdFlow0(node, cc, argAp, ap, config) and - flowCand(node, getApprox(ap), config) + flowCand(node, getApprox(ap), config) and + filter(node, ap) } + pragma[nomagic] private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and @@ -2196,7 +2230,8 @@ private module Stage4 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) ) } @@ -2448,7 +2483,7 @@ private module Stage4 { ) } - predicate revFlow(Node n, Configuration config) { revFlow(n, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { storeStepFwd(_, ap, tc, _, _, config) From d028e6b3344adb370535add2c623bbbb4be264b1 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 14:27:28 +0100 Subject: [PATCH 46/97] Dataflow: Change some headUsesContent to getHead. --- .../code/java/dataflow/internal/DataFlowImpl.qll | 12 ++++-------- .../java/dataflow/internal/DataFlowImplCommon.qll | 7 +------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 0bd01208ee3..24fbd598239 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1862,9 +1862,9 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -3300,17 +3300,13 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { fwd = true and nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0)) and - fields = - count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getFront().headUsesContent(f0))) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and tuples = count(PathNode pn) or fwd = false and nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0 and reach(pn))) and - fields = - count(TypedContent f0 | - exists(PathNodeMid pn | pn.getAp().getFront().headUsesContent(f0) 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 tuples = count(PathNode pn | reach(pn)) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index e973ea25c56..28e53dfe7b5 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -807,12 +807,7 @@ abstract class AccessPathFront extends TAccessPathFront { // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { - exists(TypedContent tc | - this.headUsesContent(tc) and - clearsContent(n, tc.getContent()) - ) - } + predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { From 293429f82162cf10191076a60b81f649762ac093 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 14:33:08 +0100 Subject: [PATCH 47/97] Dataflow: Make a bunch of the interface predicates private. --- .../java/dataflow/internal/DataFlowImpl.qll | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 24fbd598239..3f0c3e21bd5 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -774,9 +774,9 @@ private module Stage2 { } bindingset[result, ap] - ApApprox getApprox(Ap ap) { any() } + private ApApprox getApprox(Ap ap) { any() } - ApNil getApNil(Node node) { any() } + private ApNil getApNil(Node node) { any() } bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } @@ -805,21 +805,23 @@ private module Stage2 { Cc ccAny() { result = false } - class LocalCc = Unit; + private class LocalCc = Unit; bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } bindingset[call, c] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } bindingset[innercc, inner, call] - predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } bindingset[node, cc, config] - LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } - predicate localStep( + private predicate localStep( Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc ) { ( @@ -1380,9 +1382,9 @@ private module Stage3 { class ApNil = AccessPathFrontNil; - ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } @@ -1411,21 +1413,23 @@ private module Stage3 { Cc ccAny() { result = false } - class LocalCc = Unit; + private class LocalCc = Unit; bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } bindingset[call, c] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } bindingset[innercc, inner, call] - predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { any() } + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } bindingset[node, cc, config] - LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } - predicate localStep( + private predicate localStep( Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc ) { localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) @@ -2054,9 +2058,9 @@ private module Stage4 { class ApNil = AccessPathApproxNil; - ApApprox getApprox(Ap ap) { result = ap.getFront() } + private ApApprox getApprox(Ap ap) { result = ap.getFront() } - ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } bindingset[tc, tail] private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } @@ -2078,33 +2082,33 @@ private module Stage4 { Cc ccAny() { result instanceof CallContextAny } - class LocalCc = LocalCallContext; + private class LocalCc = LocalCallContext; bindingset[call, c, outercc] - CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { c = resolveCall(call, outercc) and if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() } bindingset[call, c] - CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() } bindingset[innercc, inner, call] - predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) or innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] - LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { localFlowEntry(node, config) and result = getLocalCallContext(cc, node.getEnclosingCallable()) } - predicate localStep( + private predicate localStep( Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc ) { localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) From d324cd1844302fb97e23b52df035a6640b1ecced Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 14:51:38 +0100 Subject: [PATCH 48/97] Dataflow: Some qldoc. --- .../java/dataflow/internal/DataFlowImpl.qll | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 3f0c3e21bd5..49a02e9d7ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -935,7 +935,8 @@ private module Stage2 { } /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + * 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) { @@ -992,7 +993,8 @@ private module Stage2 { } /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + * 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( @@ -1112,7 +1114,8 @@ private module Stage2 { } /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. + * 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) { @@ -1156,7 +1159,9 @@ private module Stage2 { } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * 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( @@ -1551,6 +1556,10 @@ private module Stage3 { ) } + /** + * 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 | @@ -1606,7 +1615,8 @@ private module Stage3 { } /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + * 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( @@ -1725,6 +1735,10 @@ private module Stage3 { 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(Node mid | @@ -1767,7 +1781,9 @@ private module Stage3 { } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * 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( @@ -2239,6 +2255,10 @@ private module Stage4 { ) } + /** + * 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 | @@ -2294,7 +2314,8 @@ private module Stage4 { } /** - * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + * 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( @@ -2413,6 +2434,10 @@ private module Stage4 { 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(Node mid | @@ -2455,7 +2480,9 @@ private module Stage4 { } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * 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( @@ -3315,6 +3342,11 @@ private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, i tuples = count(PathNode pn | reach(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 tuples, Configuration config ) { From e0a6a485dfe388007c8fdeee68e889d7b9ae2777 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 15:12:16 +0100 Subject: [PATCH 49/97] Dataflow: Sync. --- .../cpp/dataflow/internal/DataFlowImpl.qll | 3112 ++++++++++------- .../cpp/dataflow/internal/DataFlowImpl2.qll | 3112 ++++++++++------- .../cpp/dataflow/internal/DataFlowImpl3.qll | 3112 ++++++++++------- .../cpp/dataflow/internal/DataFlowImpl4.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImplCommon.qll | 10 +- .../dataflow/internal/DataFlowImplLocal.qll | 3112 ++++++++++------- .../cpp/ir/dataflow/internal/DataFlowImpl.qll | 3112 ++++++++++------- .../ir/dataflow/internal/DataFlowImpl2.qll | 3112 ++++++++++------- .../ir/dataflow/internal/DataFlowImpl3.qll | 3112 ++++++++++------- .../ir/dataflow/internal/DataFlowImpl4.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImplCommon.qll | 10 +- .../csharp/dataflow/internal/DataFlowImpl.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImpl2.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImpl3.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImpl4.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImpl5.qll | 3112 ++++++++++------- .../dataflow/internal/DataFlowImplCommon.qll | 10 +- .../java/dataflow/internal/DataFlowImpl2.qll | 3112 ++++++++++------- .../java/dataflow/internal/DataFlowImpl3.qll | 3112 ++++++++++------- .../java/dataflow/internal/DataFlowImpl4.qll | 3112 ++++++++++------- .../java/dataflow/internal/DataFlowImpl5.qll | 3112 ++++++++++------- .../dataflow/new/internal/DataFlowImpl.qll | 3112 ++++++++++------- .../dataflow/new/internal/DataFlowImpl2.qll | 3112 ++++++++++------- .../dataflow/new/internal/DataFlowImpl3.qll | 3112 ++++++++++------- .../dataflow/new/internal/DataFlowImpl4.qll | 3112 ++++++++++------- .../new/internal/DataFlowImplCommon.qll | 10 +- 26 files changed, 39000 insertions(+), 29504 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll index efde95bd16a..28e53dfe7b5 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll @@ -802,14 +802,12 @@ abstract class AccessPathFront extends TAccessPathFront { abstract boolean toBoolNonEmpty(); + TypedContent getHead() { this = TFrontHead(result) } + + // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { - exists(TypedContent tc | - this.headUsesContent(tc) and - clearsContent(n, tc.getContent()) - ) - } + predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 94f008a7225..49a02e9d7ad 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll index efde95bd16a..28e53dfe7b5 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll @@ -802,14 +802,12 @@ abstract class AccessPathFront extends TAccessPathFront { abstract boolean toBoolNonEmpty(); + TypedContent getHead() { this = TFrontHead(result) } + + // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { - exists(TypedContent tc | - this.headUsesContent(tc) and - clearsContent(n, tc.getContent()) - ) - } + predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 94f008a7225..49a02e9d7ad 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 94f008a7225..49a02e9d7ad 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 94f008a7225..49a02e9d7ad 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 94f008a7225..49a02e9d7ad 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 94f008a7225..49a02e9d7ad 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll index efde95bd16a..28e53dfe7b5 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll @@ -802,14 +802,12 @@ abstract class AccessPathFront extends TAccessPathFront { abstract boolean toBoolNonEmpty(); + TypedContent getHead() { this = TFrontHead(result) } + + // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { - exists(TypedContent tc | - this.headUsesContent(tc) and - clearsContent(n, tc.getContent()) - ) - } + predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 94f008a7225..49a02e9d7ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 94f008a7225..49a02e9d7ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 94f008a7225..49a02e9d7ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 94f008a7225..49a02e9d7ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll index 94f008a7225..49a02e9d7ad 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll index 94f008a7225..49a02e9d7ad 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll index 94f008a7225..49a02e9d7ad 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll index 94f008a7225..49a02e9d7ad 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll @@ -271,352 +271,384 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi */ private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call. - */ -private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { - not fullBarrier(node, config) and - ( - config.isSource(node) and - fromArg = false +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + 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. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * 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(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { + fwdFlowOut(call, node, true, config) + } + + /** + * 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) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * 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] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - localFlowStep(mid, node, config) + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - additionalLocalFlowStep(mid, node, config) + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) and - fromArg = false + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or exists(Node mid | - nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) and - fromArg = false + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false ) or // store - exists(Node mid | - useFieldFlow(config) and - nodeCandFwd1(mid, fromArg, config) and - store(mid, _, node, _) and - not outBarrier(mid, config) + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) ) or // read - exists(Content c | - nodeCandFwd1Read(c, node, fromArg, config) and - nodeCandFwd1IsStored(c, config) and - not inBarrier(node, config) + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) ) or // flow into a callable - exists(Node arg | - nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) and - fromArg = true + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) ) or // flow out of a callable - exists(DataFlowCall call | - nodeCandFwd1Out(call, node, false, config) and - fromArg = false - or - nodeCandFwd1OutFromArg(call, node, config) and - nodeCandFwd1IsEntered(call, fromArg, config) + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) - ) -} + } -private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, 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(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } -pragma[nomagic] -private predicate nodeCandFwd1Read(Content c, Node node, boolean fromArg, Configuration config) { - exists(Node mid | - nodeCandFwd1(mid, fromArg, config) and - read(mid, c, node) - ) -} + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsStored(Content c, Configuration config) { - exists(Node mid, Node node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - nodeCandFwd1(mid, config) and - store(mid, tc, node, _) and - c = tc.getContent() - ) -} + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } -pragma[nomagic] -private predicate nodeCandFwd1ReturnPosition( - ReturnPosition pos, boolean fromArg, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCandFwd1(ret, fromArg, config) and - getReturnPosition(ret) = pos - ) -} - -pragma[nomagic] -private predicate nodeCandFwd1Out(DataFlowCall call, Node out, boolean fromArg, Configuration config) { - exists(ReturnPosition pos | - nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and viableReturnPosOut(call, pos, out) - ) -} + } -pragma[nomagic] -private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { - nodeCandFwd1Out(call, node, true, config) -} + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. - */ -pragma[nomagic] -private predicate nodeCandFwd1IsEntered(DataFlowCall call, boolean fromArg, Configuration config) { - exists(ArgumentNode arg | - nodeCandFwd1(arg, fromArg, config) and - viableParamArg(call, _, arg) - ) + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand1(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand1( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand1(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. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand1(p, config) and + returnFlowCallableNodeCand1(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ } bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } -/** - * 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 nodeCand1(Node node, boolean toReturn, Configuration config) { - nodeCand1_0(node, toReturn, config) and - nodeCandFwd1(node, config) -} - -pragma[nomagic] -private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { - nodeCandFwd1(node, config) and - config.isSink(node) and - toReturn = false - or - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, toReturn, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - nodeCand1Store(c, node, toReturn, config) and - nodeCand1IsRead(c, config) - ) - or - // read - exists(Node mid, Content c | - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, toReturn, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - nodeCand1In(call, node, false, config) and - toReturn = false - or - nodeCand1InToReturn(call, node, config) and - nodeCand1IsReturned(call, toReturn, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1Out(pos, config) and - getReturnPosition(node) = pos and - toReturn = true - ) -} - -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsRead(Content c, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd1(node, unbind(config)) and - read(node, c, mid) and - nodeCandFwd1IsStored(c, unbind(config)) and - nodeCand1(mid, _, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1Store(Content c, Node node, boolean toReturn, Configuration config) { - exists(Node mid, TypedContent tc | - nodeCand1(mid, toReturn, config) and - nodeCandFwd1IsStored(c, unbind(config)) and - store(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 `nodeCand1`. - */ -private predicate nodeCand1IsReadAndStored(Content c, Configuration conf) { - nodeCand1IsRead(c, conf) and - nodeCand1Store(c, _, _, conf) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, Node out, Configuration config -) { - nodeCandFwd1ReturnPosition(pos, _, config) and - viableReturnPosOut(call, pos, out) -} - -pragma[nomagic] -private predicate nodeCand1Out(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, Node out | - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) -} - -pragma[nomagic] -private predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config -) { - viableParamArg(call, p, arg) and - nodeCandFwd1(arg, config) -} - -pragma[nomagic] -private predicate nodeCand1In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config -) { - exists(ParameterNode p | - nodeCand1(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1InToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { - nodeCand1In(call, arg, true, config) -} - -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. - */ -pragma[nomagic] -private predicate nodeCand1IsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(Node out | - nodeCand1(out, toReturn, config) and - nodeCandFwd1OutFromArg(call, out, config) - ) -} - -pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } - -private predicate throughFlowNodeCand1(Node node, Configuration config) { - nodeCand1(node, true, config) and - nodeCandFwd1(node, true, config) and - not fullBarrier(node, config) and - not inBarrier(node, config) and - not outBarrier(node, config) -} - -/** Holds if flow may return from `callable`. */ -pragma[nomagic] -private predicate returnFlowCallableNodeCand1( - DataFlowCallable callable, ReturnKindExt kind, Configuration config -) { - exists(ReturnNodeExt ret | - throughFlowNodeCand1(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. - */ -private predicate parameterThroughFlowNodeCand1(ParameterNode p, Configuration config) { - exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(p.getEnclosingCallable(), kind, config) and - // we don't expect a parameter to return stored in itself - not exists(int pos | - kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) - ) - ) -} - -pragma[nomagic] -private predicate storeCand1(Node n1, Content c, Node n2, Configuration config) { - exists(TypedContent tc | - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - store(n1, tc, n2, _) and - c = tc.getContent() - ) -} - -pragma[nomagic] -private predicate read(Node n1, Content c, Node n2, Configuration config) { - nodeCand1IsReadAndStored(c, config) and - nodeCand1(n2, unbind(config)) and - read(n1, c, n2) -} - pragma[noinline] private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and localFlowStep(node1, node2, config) } pragma[noinline] private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { - nodeCand1(node1, config) and + Stage1::revFlow(node2, config) and additionalLocalFlowStep(node1, node2, config) } @@ -624,8 +656,8 @@ pragma[nomagic] private predicate viableReturnPosOutNodeCand1( DataFlowCall call, ReturnPosition pos, Node out, Configuration config ) { - nodeCand1(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) } /** @@ -638,7 +670,7 @@ private predicate flowOutOfCallNodeCand1( DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config ) { viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and - nodeCand1(ret, config) and + Stage1::revFlow(ret, config) and not outBarrier(ret, config) and not inBarrier(out, config) } @@ -647,8 +679,8 @@ pragma[nomagic] private predicate viableParamArgNodeCand1( DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config ) { - viableParamArgNodeCandFwd1(call, p, arg, config) and - nodeCand1(arg, config) + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) } /** @@ -660,7 +692,7 @@ private predicate flowIntoCallNodeCand1( DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and - nodeCand1(p, config) and + Stage1::revFlow(p, config) and not outBarrier(arg, config) and not inBarrier(p, config) } @@ -730,338 +762,491 @@ private predicate flowIntoCallNodeCand1( ) } -/** - * Holds if `node` is reachable from a source in the configuration `config`. - * The Boolean `stored` records whether the tracked value is stored into a - * field of `node`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argStored` records whether the tracked - * value was stored into a field of the argument. - */ -private predicate nodeCandFwd2( - Node node, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - nodeCand1(node, config) and - config.isSource(node) and - fromArg = false and - argStored = TBooleanNone() and - stored = false - or - nodeCand1(node, unbind(config)) and - ( - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - localFlowStepNodeCand1(mid, node, config) +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, stored, config) and - additionalLocalFlowStepNodeCand1(mid, node, config) and - stored = false - ) - or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid | - nodeCandFwd2(mid, _, _, stored, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - fromArg = false and - argStored = TBooleanNone() and - stored = false + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or // store - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, _, config) and - storeCand1(mid, _, node, config) and - stored = true + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) or // read - exists(Content c | - nodeCandFwd2Read(c, node, fromArg, argStored, config) and - nodeCandFwd2IsStored(c, stored, config) + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) ) or // flow into a callable - nodeCandFwd2In(_, node, _, _, stored, config) and - fromArg = true and - if parameterThroughFlowNodeCand1(node, config) - then argStored = TBooleanSome(stored) - else argStored = TBooleanNone() + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) or // flow out of a callable exists(DataFlowCall call | - nodeCandFwd2Out(call, node, fromArg, argStored, stored, config) and - fromArg = false + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - exists(boolean argStored0 | - nodeCandFwd2OutFromArg(call, node, argStored0, stored, config) and - nodeCandFwd2IsEntered(call, fromArg, argStored, argStored0, config) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) ) - ) -} + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCandFwd2`. - */ -pragma[noinline] -private predicate nodeCandFwd2IsStored(Content c, boolean stored, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCand1(node, unbind(config)) and - nodeCandFwd2(mid, _, _, stored, config) and - storeCand1(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Read( - Content c, Node node, boolean fromArg, BooleanOption argStored, Configuration config -) { - exists(Node mid | - nodeCandFwd2(mid, fromArg, argStored, true, config) and - read(mid, c, node, config) - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2In( - DataFlowCall call, ParameterNode p, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - nodeCandFwd2(arg, fromArg, argStored, stored, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2Out( - DataFlowCall call, Node out, boolean fromArg, BooleanOption argStored, boolean stored, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - nodeCandFwd2(ret, fromArg, argStored, stored, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - stored = false or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate nodeCandFwd2OutFromArg( - DataFlowCall call, Node out, boolean argStored, boolean stored, Configuration config -) { - nodeCandFwd2Out(call, out, true, TBooleanSome(argStored), stored, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd2`. - */ -pragma[nomagic] -private predicate nodeCandFwd2IsEntered( - DataFlowCall call, boolean fromArg, BooleanOption argStored, boolean stored, Configuration config -) { - exists(ParameterNode p | - nodeCandFwd2In(call, p, fromArg, argStored, stored, config) and - parameterThroughFlowNodeCand1(p, config) - ) -} - -/** - * Holds if `node` is part of a path from a source to a sink in the - * configuration `config`. The Boolean `read` records whether the tracked - * value must be read from a field of `node` in order to reach a sink. - * - * The Boolean `toReturn` records whether the node must be returned from - * the enclosing callable in order to reach a sink, and if so, `returnRead` - * records whether a field must be read from the returned value. - */ -private predicate nodeCand2( - Node node, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - nodeCandFwd2(node, _, _, false, config) and - config.isSink(node) and - toReturn = false and - returnRead = TBooleanNone() and - read = false - or - nodeCandFwd2(node, _, _, unbindBool(read), unbind(config)) and - ( - exists(Node mid | - localFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), 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 fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil or exists(Node mid | - additionalLocalFlowStepNodeCand1(node, mid, config) and - nodeCand2(mid, toReturn, returnRead, read, config) and - read = false + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, ap, config) and toReturn = false and - returnRead = TBooleanNone() + returnAp = apNone() ) or - exists(Node mid | + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and additionalJumpStep(node, mid, config) and - nodeCand2(mid, _, _, read, config) and + revFlow(mid, _, _, nil, config) and toReturn = false and - returnRead = TBooleanNone() and - read = false + returnAp = apNone() and + ap instanceof ApNil ) or // store - exists(Content c | - nodeCand2Store(c, node, toReturn, returnRead, read, config) and - nodeCand2IsRead(c, read, config) + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) ) or // read - exists(Node mid, Content c, boolean read0 | - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read0), unbind(config)) and - nodeCand2(mid, toReturn, returnRead, read0, config) and - read = true + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) ) or // flow into a callable exists(DataFlowCall call | - nodeCand2In(call, node, toReturn, returnRead, read, config) and + revFlowIn(call, node, toReturn, returnAp, ap, config) and toReturn = false or - exists(boolean returnRead0 | - nodeCand2InToReturn(call, node, returnRead0, read, config) and - nodeCand2IsReturned(call, toReturn, returnRead, returnRead0, config) + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) ) or // flow out of a callable - nodeCand2Out(_, node, _, _, read, config) and + revFlowOut(_, node, _, _, ap, config) and toReturn = true and - if nodeCandFwd2(node, true, TBooleanSome(_), unbindBool(read), config) - then returnRead = TBooleanSome(read) - else returnRead = TBooleanNone() - ) -} + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -/** - * Holds if `c` is the target of a read in the flow covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsRead(Content c, boolean read, Configuration config) { - exists(Node mid, Node node | - useFieldFlow(config) and - nodeCandFwd2(node, _, _, true, unbind(config)) and - read(node, c, mid, config) and - nodeCandFwd2IsStored(c, unbindBool(read), unbind(config)) and - nodeCand2(mid, _, _, read, config) - ) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate nodeCand2Store( - Content c, Node node, boolean toReturn, BooleanOption returnRead, boolean stored, - Configuration config -) { - exists(Node mid | - storeCand1(node, c, mid, config) and - nodeCand2(mid, toReturn, returnRead, true, config) and - nodeCandFwd2(node, _, _, stored, unbind(config)) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -/** - * Holds if `c` is the target of a store in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsStored(Content c, boolean stored, Configuration conf) { - exists(Node node | - nodeCand2Store(c, node, _, _, stored, conf) and - nodeCand2(node, _, _, stored, conf) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -/** - * Holds if `c` is the target of both a store and a read in the path graph - * covered by `nodeCand2`. - */ -pragma[noinline] -private predicate nodeCand2IsReadAndStored(Content c, Configuration conf) { - exists(boolean apNonEmpty | - nodeCand2IsStored(c, apNonEmpty, conf) and - nodeCand2IsRead(c, apNonEmpty, conf) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate nodeCand2Out( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - nodeCand2(out, toReturn, returnRead, read, config) and - flowOutOfCallNodeCand1(call, ret, out, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate nodeCand2In( - DataFlowCall call, ArgumentNode arg, boolean toReturn, BooleanOption returnRead, boolean read, - Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - nodeCand2(p, toReturn, returnRead, read, config) and - flowIntoCallNodeCand1(call, arg, p, allowsFieldFlow, config) - | - read = false or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate nodeCand2InToReturn( - DataFlowCall call, ArgumentNode arg, boolean returnRead, boolean read, Configuration config -) { - nodeCand2In(call, arg, true, TBooleanSome(returnRead), read, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `nodeCand2`. - */ -pragma[nomagic] -private predicate nodeCand2IsReturned( - DataFlowCall call, boolean toReturn, BooleanOption returnRead, boolean read, Configuration config -) { - exists(ReturnNodeExt ret | - nodeCand2Out(call, ret, toReturn, returnRead, read, config) and - nodeCandFwd2(ret, true, TBooleanSome(_), read, config) - ) -} + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } -private predicate nodeCand2(Node node, Configuration config) { nodeCand2(node, _, _, _, config) } + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} pragma[nomagic] private predicate flowOutOfCallNodeCand2( DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config ) { flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } pragma[nomagic] @@ -1070,8 +1255,8 @@ private predicate flowIntoCallNodeCand2( Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - nodeCand2(node2, config) and - nodeCand2(node1, unbind(config)) + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) } private module LocalFlowBigStep { @@ -1091,7 +1276,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ predicate localFlowEntry(Node node, Configuration config) { - nodeCand2(node, config) and + Stage2::revFlow(node, config) and ( config.isSource(node) or jumpStep(_, node, config) or @@ -1109,7 +1294,7 @@ private module LocalFlowBigStep { * flow steps in a dataflow path. */ private predicate localFlowExit(Node node, Configuration config) { - exists(Node next | nodeCand2(next, config) | + exists(Node next | Stage2::revFlow(next, config) | jumpStep(node, next, config) or additionalJumpStep(node, next, config) or flowIntoCallNodeCand1(_, node, next, config) or @@ -1126,8 +1311,8 @@ private module LocalFlowBigStep { pragma[noinline] private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { additionalLocalFlowStepNodeCand1(node1, node2, config) and - nodeCand2(node1, _, _, false, config) and - nodeCand2(node2, _, _, false, unbind(config)) + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) } /** @@ -1157,13 +1342,13 @@ private module LocalFlowBigStep { node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) or exists(Node mid | localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStepNodeCand1(mid, node2, config) and not mid instanceof FlowCheckNode and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) or exists(Node mid | @@ -1172,7 +1357,7 @@ private module LocalFlowBigStep { not mid instanceof FlowCheckNode and preservesValue = false and t = getNodeType(node2) and - nodeCand2(node2, unbind(config)) + Stage2::revFlow(node2, unbind(config)) ) ) } @@ -1193,358 +1378,488 @@ private module LocalFlowBigStep { private import LocalFlowBigStep -pragma[nomagic] -private predicate readCand2(Node node1, Content c, Node node2, Configuration config) { - read(node1, c, node2, config) and - nodeCand2(node1, _, _, true, unbind(config)) and - nodeCand2(node2, config) and - nodeCand2IsReadAndStored(c, unbind(config)) -} +private module Stage3 { + module PrevStage = Stage2; -pragma[nomagic] -private predicate storeCand2( - Node node1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config -) { - store(node1, tc, node2, contentType) and - nodeCand2(node1, config) and - nodeCand2(node2, _, _, true, unbind(config)) and - nodeCand2IsReadAndStored(tc.getContent(), unbind(config)) -} + class ApApprox = PrevStage::Ap; -/** - * Holds if `node` is reachable with access path front `apf` from a - * source in the configuration `config`. - * - * The Boolean `fromArg` records whether the node is reached through an - * argument in a call, and if so, `argApf` records the front of the - * access path of that argument. - */ -pragma[nomagic] -private predicate flowCandFwd( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd0(node, fromArg, argApf, apf, config) and - not apf.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any() -} + class Ap = AccessPathFront; -pragma[nomagic] -private predicate flowCandFwd0( - Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - nodeCand2(node, _, _, false, config) and - config.isSource(node) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - or - exists(Node mid | - flowCandFwd(mid, fromArg, argApf, apf, config) and - localFlowBigStep(mid, node, true, _, config, _) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, fromArg, argApf, nil, config) and - localFlowBigStep(mid, node, false, apf, config, _) - ) - or - exists(Node mid | - flowCandFwd(mid, _, _, apf, config) and - nodeCand2(node, unbind(config)) and - jumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(mid, _, _, nil, config) and - nodeCand2(node, unbind(config)) and - additionalJumpStep(mid, node, config) and - fromArg = false and - argApf = TAccessPathFrontNone() and - apf = TFrontNil(getNodeType(node)) - ) - or - // store - exists(Node mid, TypedContent tc, AccessPathFront apf0, DataFlowType contentType | - flowCandFwd(mid, fromArg, argApf, apf0, config) and - storeCand2(mid, tc, node, contentType, config) and - nodeCand2(node, _, _, true, unbind(config)) and - apf.headUsesContent(tc) and - compatibleTypes(apf0.getType(), contentType) - ) - or - // read - exists(TypedContent tc | - flowCandFwdRead(tc, node, fromArg, argApf, config) and - flowCandFwdConsCand(tc, apf, config) and - nodeCand2(node, _, _, unbindBool(apf.toBoolNonEmpty()), unbind(config)) - ) - or - // flow into a callable - flowCandFwdIn(_, node, _, _, apf, config) and - fromArg = true and - if nodeCand2(node, true, _, unbindBool(apf.toBoolNonEmpty()), config) - then argApf = TAccessPathFrontSome(apf) - else argApf = TAccessPathFrontNone() - or - // flow out of a callable - exists(DataFlowCall call | - flowCandFwdOut(call, node, fromArg, argApf, apf, config) and - fromArg = false + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccAny() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private 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) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) or - exists(AccessPathFront argApf0 | - flowCandFwdOutFromArg(call, node, argApf0, apf, config) and - flowCandFwdIsEntered(call, fromArg, argApf, argApf0, config) + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) - ) -} - -pragma[nomagic] -private predicate flowCandFwdConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - exists(Node mid, Node n, DataFlowType contentType | - flowCandFwd(mid, _, _, apf, config) and - storeCand2(mid, tc, n, contentType, config) and - nodeCand2(n, _, _, true, unbind(config)) and - compatibleTypes(apf.getType(), contentType) - ) -} - -pragma[nomagic] -private predicate flowCandFwdRead0( - Node node1, TypedContent tc, Content c, Node node2, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFrontHead apf, Configuration config -) { - flowCandFwd(node1, fromArg, argApf, apf, config) and - readCand2(node1, c, node2, config) and - apf.headUsesContent(tc) -} - -pragma[nomagic] -private predicate flowCandFwdRead( - TypedContent tc, Node node, boolean fromArg, AccessPathFrontOption argApf, Configuration config -) { - flowCandFwdRead0(_, tc, tc.getContent(), node, fromArg, argApf, _, config) -} - -pragma[nomagic] -private predicate flowCandFwdIn( - DataFlowCall call, ParameterNode p, boolean fromArg, AccessPathFrontOption argApf, - AccessPathFront apf, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow | - flowCandFwd(arg, fromArg, argApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOut( - DataFlowCall call, Node node, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowCandFwd(ret, fromArg, argApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowCandFwdOutFromArg( - DataFlowCall call, Node node, AccessPathFront argApf, AccessPathFront apf, Configuration config -) { - flowCandFwdOut(call, node, true, TAccessPathFrontSome(argApf), apf, config) -} - -/** - * Holds if an argument to `call` is reached in the flow covered by `flowCandFwd`. - */ -pragma[nomagic] -private predicate flowCandFwdIsEntered( - DataFlowCall call, boolean fromArg, AccessPathFrontOption argApf, AccessPathFront apf, - Configuration config -) { - exists(ParameterNode p | - flowCandFwdIn(call, p, fromArg, argApf, apf, config) and - nodeCand2(p, true, TBooleanSome(_), unbindBool(apf.toBoolNonEmpty()), config) - ) -} - -/** - * Holds if `node` with access path front `apf` 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, and if so, `returnApf` - * records the front of the access path of the returned value. - */ -pragma[nomagic] -private predicate flowCand( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCand0(node, toReturn, returnApf, apf, config) and - flowCandFwd(node, _, _, apf, config) -} - -pragma[nomagic] -private predicate flowCand0( - Node node, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - flowCandFwd(node, _, _, apf, config) and - config.isSink(node) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flowCand(mid, toReturn, returnApf, apf, config) - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flowCand(mid, toReturn, returnApf, nil, config) and - apf instanceof AccessPathFrontNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flowCand(mid, _, _, apf, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() - ) - or - exists(Node mid, AccessPathFrontNil nil | - flowCandFwd(node, _, _, apf, config) and - additionalJumpStep(node, mid, config) and - flowCand(mid, _, _, nil, config) and - toReturn = false and - returnApf = TAccessPathFrontNone() and - apf instanceof AccessPathFrontNil - ) - or - // store - exists(TypedContent tc | - flowCandStore(node, tc, apf, toReturn, returnApf, config) and - flowCandConsCand(tc, apf, config) - ) - or - // read - exists(TypedContent tc, AccessPathFront apf0 | - flowCandRead(node, tc, apf, toReturn, returnApf, apf0, config) and - flowCandFwdConsCand(tc, apf0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowCandIn(call, node, toReturn, returnApf, apf, config) and - toReturn = false or - exists(AccessPathFront returnApf0 | - flowCandInToReturn(call, node, returnApf0, apf, config) and - flowCandIsReturned(call, toReturn, returnApf, returnApf0, config) + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() ) - ) - or - // flow out of a callable - flowCandOut(_, node, _, _, apf, config) and - toReturn = true and - if flowCandFwd(node, true, _, apf, config) - then returnApf = TAccessPathFrontSome(apf) - else returnApf = TAccessPathFrontNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } -pragma[nomagic] -private predicate readCandFwd( - Node node1, TypedContent tc, AccessPathFront apf, Node node2, Configuration config -) { - flowCandFwdRead0(node1, tc, tc.getContent(), node2, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -pragma[nomagic] -private predicate flowCandRead( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, AccessPathFront apf0, Configuration config -) { - exists(Node mid | - readCandFwd(node, tc, apf, mid, config) and - flowCand(mid, toReturn, returnApf, apf0, config) - ) -} + /** + * 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 flowCandStore( - Node node, TypedContent tc, AccessPathFront apf, boolean toReturn, - AccessPathFrontOption returnApf, Configuration config -) { - exists(Node mid | - flowCandFwd(node, _, _, apf, config) and - storeCand2(node, tc, mid, _, unbind(config)) and - flowCand(mid, toReturn, returnApf, TFrontHead(tc), unbind(config)) - ) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -pragma[nomagic] -private predicate flowCandConsCand(TypedContent tc, AccessPathFront apf, Configuration config) { - flowCandFwdConsCand(tc, apf, config) and - flowCandRead(_, tc, _, _, _, apf, config) -} + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flowCand(out, toReturn, returnApf, apf, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowCandIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathFrontOption returnApf, - AccessPathFront apf, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flowCand(p, toReturn, returnApf, apf, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apf instanceof AccessPathFrontNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + } -pragma[nomagic] -private predicate flowCandInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathFront returnApf, AccessPathFront apf, - Configuration config -) { - flowCandIn(call, arg, true, TAccessPathFrontSome(returnApf), apf, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flowCand`. - */ -pragma[nomagic] -private predicate flowCandIsReturned( - DataFlowCall call, boolean toReturn, AccessPathFrontOption returnApf, AccessPathFront apf, - Configuration config -) { - exists(ReturnNodeExt ret | - flowCandOut(call, ret, toReturn, returnApf, apf, config) and - flowCandFwd(ret, true, TAccessPathFrontSome(_), apf, config) - ) + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ } /** @@ -1553,8 +1868,8 @@ private predicate flowCandIsReturned( */ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { exists(AccessPathFront apf | - flowCand(node, true, _, apf, config) and - flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config) + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) ) } @@ -1564,12 +1879,12 @@ private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configur */ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and nodes = strictcount(Node n | - flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config) + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) ) and accessPathApproxCostLimits(apLimit, tupleLimit) and apLimit < tails and @@ -1580,11 +1895,11 @@ private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) private newtype TAccessPathApprox = TNil(DataFlowType t) or TConsNil(TypedContent tc, DataFlowType t) { - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and not expensiveLen2unfolding(tc, _) } or TConsCons(TypedContent tc1, TypedContent tc2, int len) { - flowCandConsCand(tc1, TFrontHead(tc2), _) and + Stage3::consCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()] and not expensiveLen2unfolding(tc1, _) } or @@ -1714,7 +2029,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { override AccessPathApprox pop(TypedContent head) { head = tc and ( - exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) | + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | result = TConsCons(tc2, _, len - 1) or len = 2 and @@ -1725,7 +2040,7 @@ private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { or exists(DataFlowType t | len = 1 and - flowCandConsCand(tc, TFrontNil(t), _) and + Stage3::consCand(tc, TFrontNil(t), _) and result = TNil(t) ) ) @@ -1750,435 +2065,519 @@ private class AccessPathApproxOption extends TAccessPathApproxOption { } } -/** - * Holds if `node` is reachable with approximate access path `apa` 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, `argApa` records the approximate access path - * of that argument. - */ -private predicate flowFwd( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowFwd0(node, cc, argApa, apf, apa, config) and - flowCand(node, _, _, apf, config) -} +private module Stage4 { + module PrevStage = Stage3; -private predicate flowFwd0( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, Configuration config -) { - flowCand(node, _, _, _, config) and - config.isSource(node) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - or - flowCand(node, _, _, _, unbind(config)) and - ( - exists(Node mid, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, apf, apa, localCC, config) and - localFlowBigStep(mid, node, true, _, config, localCC) - ) + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccAny() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) or - exists(Node mid, AccessPathApproxNil nil, LocalCallContext localCC | - flowFwdLocalEntry(mid, cc, argApa, _, nil, localCC, config) and - localFlowBigStep(mid, node, false, apf, config, localCC) and - apf = apa.(AccessPathApproxNil).getFront() + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * 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, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil ) or exists(Node mid | - flowFwd(mid, _, _, apf, apa, config) and + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() + cc = ccAny() and + argAp = apNone() ) or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(mid, _, _, _, nil, config) and + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc instanceof CallContextAny and - argApa = TAccessPathApproxNone() and - apa = TNil(getNodeType(node)) and - apf = apa.(AccessPathApproxNil).getFront() - ) - ) - or - // store - exists(TypedContent tc | flowFwdStore(node, tc, pop(tc, apa), apf, cc, argApa, config)) - or - // read - exists(TypedContent tc | - flowFwdRead(node, _, push(tc, apa), apf, cc, argApa, config) and - flowFwdConsCand(tc, apf, apa, config) - ) - or - // flow into a callable - flowFwdIn(_, node, _, cc, _, apf, apa, config) and - if flowCand(node, true, _, apf, config) - then argApa = TAccessPathApproxSome(apa) - else argApa = TAccessPathApproxNone() - or - // flow out of a callable - exists(DataFlowCall call | - exists(DataFlowCallable c | - flowFwdOut(call, node, any(CallContextNoCall innercc), c, argApa, apf, apa, config) and - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + cc = ccAny() and + argAp = apNone() and + ap = getApNil(node) ) or - exists(AccessPathApprox argApa0 | - flowFwdOutFromArg(call, node, argApa0, apf, apa, config) and - flowFwdIsEntered(call, cc, argApa, argApa0, config) + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) ) - ) -} - -pragma[nomagic] -private predicate flowFwdLocalEntry( - Node node, CallContext cc, AccessPathApproxOption argApa, AccessPathFront apf, - AccessPathApprox apa, LocalCallContext localCC, Configuration config -) { - flowFwd(node, cc, argApa, apf, apa, config) and - localFlowEntry(node, config) and - localCC = getLocalCallContext(cc, node.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate flowFwdStore( - Node node, TypedContent tc, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, AccessPathFront apf0 | - flowFwd(mid, cc, argApa, apf0, apa0, config) and - flowFwdStore0(mid, tc, node, apf0, apf, config) - ) -} - -pragma[nomagic] -private predicate storeCand( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFront apf, - Configuration config -) { - storeCand2(mid, tc, node, _, config) and - flowCand(mid, _, _, apf0, config) and - apf.headUsesContent(tc) -} - -pragma[noinline] -private predicate flowFwdStore0( - Node mid, TypedContent tc, Node node, AccessPathFront apf0, AccessPathFrontHead apf, - Configuration config -) { - storeCand(mid, tc, node, apf0, apf, config) and - flowCandConsCand(tc, apf0, config) and - flowCand(node, _, _, apf, unbind(config)) -} - -pragma[nomagic] -private predicate flowFwdRead0( - Node node1, TypedContent tc, AccessPathFrontHead apf0, AccessPathApprox apa0, Node node2, - CallContext cc, AccessPathApproxOption argApa, Configuration config -) { - flowFwd(node1, cc, argApa, apf0, apa0, config) and - readCandFwd(node1, tc, apf0, node2, config) -} - -pragma[nomagic] -private predicate flowFwdRead( - Node node, AccessPathFrontHead apf0, AccessPathApprox apa0, AccessPathFront apf, CallContext cc, - AccessPathApproxOption argApa, Configuration config -) { - exists(Node mid, TypedContent tc | - flowFwdRead0(mid, tc, apf0, apa0, node, cc, argApa, config) and - flowCand(node, _, _, apf, unbind(config)) and - flowCandConsCand(tc, apf, unbind(config)) - ) -} - -pragma[nomagic] -private predicate flowFwdConsCand( - TypedContent tc, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(Node n | - flowFwd(n, _, _, apf, apa, config) and - flowFwdStore0(n, tc, _, apf, _, config) - ) -} - -pragma[nomagic] -private predicate flowFwdIn( - DataFlowCall call, ParameterNode p, CallContext outercc, CallContext innercc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ArgumentNode arg, boolean allowsFieldFlow, DataFlowCallable c | - flowFwd(arg, outercc, argApa, apf, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) and - c = p.getEnclosingCallable() and - c = resolveCall(call, outercc) and - flowCand(p, _, _, _, unbind(config)) and - if recordDataFlowCallSite(call, c) then innercc = TSpecificCall(call) else innercc = TSomeCall() - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} - -pragma[nomagic] -private predicate flowFwdOut( - DataFlowCall call, Node node, CallContext innercc, DataFlowCallable innerc, - AccessPathApproxOption argApa, AccessPathFront apf, AccessPathApprox apa, Configuration config -) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow | - flowFwd(ret, innercc, argApa, apf, apa, config) and - flowOutOfCallNodeCand2(call, ret, node, allowsFieldFlow, config) and - innerc = ret.getEnclosingCallable() and - flowCand(node, _, _, _, unbind(config)) and - ( - resolveReturn(innercc, innerc, call) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) or - innercc.(CallContextCall).matchesCall(call) + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) ) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + } -pragma[nomagic] -private predicate flowFwdOutFromArg( - DataFlowCall call, Node node, AccessPathApprox argApa, AccessPathFront apf, AccessPathApprox apa, - Configuration config -) { - flowFwdOut(call, node, any(CallContextCall ccc), _, TAccessPathApproxSome(argApa), apf, apa, - config) -} + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } -/** - * Holds if an argument to `call` is reached in the flow covered by `flowFwd`. - */ -pragma[nomagic] -private predicate flowFwdIsEntered( - DataFlowCall call, CallContext cc, AccessPathApproxOption argApa, AccessPathApprox apa, - Configuration config -) { - exists(ParameterNode p, AccessPathFront apf | - flowFwdIn(call, p, cc, _, argApa, apf, apa, config) and - flowCand(p, true, TAccessPathFrontSome(_), apf, config) - ) -} + /** + * 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) + ) + } -/** - * Holds if `node` with approximate access path `apa` 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, and if so, `returnApa` - * records the approximate access path of the returned value. - */ -private predicate flow( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flow0(node, toReturn, returnApa, apa, config) and - flowFwd(node, _, _, _, apa, config) -} + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } -private predicate flow0( - Node node, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - flowFwd(node, _, _, _, apa, config) and - config.isSink(node) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - or - exists(Node mid | - localFlowBigStep(node, mid, true, _, config, _) and - flow(mid, toReturn, returnApa, apa, config) - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - localFlowBigStep(node, mid, false, _, config, _) and - flow(mid, toReturn, returnApa, nil, config) and - apa instanceof AccessPathApproxNil - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - flow(mid, _, _, apa, config) and + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, 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, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node 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(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` 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, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and toReturn = false and - returnApa = TAccessPathApproxNone() - ) - or - exists(Node mid, AccessPathApproxNil nil | - flowFwd(node, _, _, _, apa, config) and - additionalJumpStep(node, mid, config) and - flow(mid, _, _, nil, config) and - toReturn = false and - returnApa = TAccessPathApproxNone() and - apa instanceof AccessPathApproxNil - ) - or - // store - exists(TypedContent tc | - flowStore(tc, node, toReturn, returnApa, apa, config) and - flowConsCand(tc, apa, config) - ) - or - // read - exists(Node mid, AccessPathApprox apa0 | - readFlowFwd(node, _, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) - or - // flow into a callable - exists(DataFlowCall call | - flowIn(call, node, toReturn, returnApa, apa, config) and - toReturn = false + returnAp = apNone() and + ap instanceof ApNil or - exists(AccessPathApprox returnApa0 | - flowInToReturn(call, node, returnApa0, apa, config) and - flowIsReturned(call, toReturn, returnApa, returnApa0, config) + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) ) - ) - or - // flow out of a callable - flowOut(_, node, _, _, apa, config) and - toReturn = true and - if flowFwd(node, any(CallContextCall ccc), TAccessPathApproxSome(_), _, apa, config) - then returnApa = TAccessPathApproxSome(apa) - else returnApa = TAccessPathApproxNone() -} + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } -pragma[nomagic] -private predicate storeFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - storeCand2(node1, tc, node2, _, config) and - flowFwdStore(node2, tc, apa, _, _, _, config) and - apa0 = push(tc, apa) -} + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } -pragma[nomagic] -private predicate flowStore( - TypedContent tc, Node node, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node mid, AccessPathApprox apa0 | - storeFlowFwd(node, tc, mid, apa, apa0, config) and - flow(mid, toReturn, returnApa, apa0, config) - ) -} + /** + * 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(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } -pragma[nomagic] -private predicate readFlowFwd( - Node node1, TypedContent tc, Node node2, AccessPathApprox apa, AccessPathApprox apa0, - Configuration config -) { - exists(AccessPathFrontHead apf | - readCandFwd(node1, tc, apf, node2, config) and - flowFwdRead(node2, apf, apa, _, _, _, config) and - apa0 = pop(tc, apa) and - flowFwdConsCand(tc, _, apa0, unbind(config)) - ) -} + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowConsCand(TypedContent tc, AccessPathApprox apa, Configuration config) { - exists(Node n, Node mid | - flow(mid, _, _, apa, config) and - readFlowFwd(n, tc, mid, _, apa, config) - ) -} + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } -pragma[nomagic] -private predicate flowOut( - DataFlowCall call, ReturnNodeExt ret, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(Node out, boolean allowsFieldFlow | - flow(out, toReturn, returnApa, apa, config) and - flowOutOfCallNodeCand2(call, ret, out, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } -pragma[nomagic] -private predicate flowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, AccessPathApproxOption returnApa, - AccessPathApprox apa, Configuration config -) { - exists(ParameterNode p, boolean allowsFieldFlow | - flow(p, toReturn, returnApa, apa, config) and - flowIntoCallNodeCand2(call, arg, p, allowsFieldFlow, config) - | - apa instanceof AccessPathApproxNil or allowsFieldFlow = true - ) -} + /** + * 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, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } -pragma[nomagic] -private predicate flowInToReturn( - DataFlowCall call, ArgumentNode arg, AccessPathApprox returnApa, AccessPathApprox apa, - Configuration config -) { - flowIn(call, arg, true, TAccessPathApproxSome(returnApa), apa, config) -} + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } -/** - * Holds if an output from `call` is reached in the flow covered by `flow`. - */ -pragma[nomagic] -private predicate flowIsReturned( - DataFlowCall call, boolean toReturn, AccessPathApproxOption returnApa, AccessPathApprox apa, - Configuration config -) { - exists(ReturnNodeExt ret, CallContextCall ccc | - flowOut(call, ret, toReturn, returnApa, apa, config) and - flowFwd(ret, ccc, TAccessPathApproxSome(_), _, apa, config) and - ccc.matchesCall(call) - ) + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ } bindingset[conf, result] private Configuration unbind(Configuration conf) { result >= conf and result <= conf } -private predicate flow(Node n, Configuration config) { flow(n, _, _, _, config) } - -pragma[noinline] -private predicate parameterFlow( - ParameterNode p, AccessPathApprox apa, AccessPathApprox apa0, DataFlowCallable c, - Configuration config -) { - flow(p, true, TAccessPathApproxSome(apa0), apa, config) and - c = p.getEnclosingCallable() -} - -private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, AccessPathApprox apa) { - exists(ReturnNodeExt ret, Configuration config, AccessPathApprox apa0 | - parameterFlow(p, apa, apa0, c, config) and - c = ret.getEnclosingCallable() and - flow(ret, true, TAccessPathApproxSome(_), apa0, config) and - flowFwd(ret, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) - ) -} - private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { exists(DataFlowCallable c, AccessPathApprox apa0 | - parameterMayFlowThrough(_, c, apa) and - flow(n, true, _, apa0, config) and - flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, config) and + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and n.getEnclosingCallable() = c ) } private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) } + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } /** * A context for generating flow summaries. This represents flow entry through @@ -2222,14 +2621,15 @@ private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { len = apa.len() and result = strictcount(AccessPathFront apf | - flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), config) ) ) } private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = strictcount(Node n | flow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) } /** @@ -2249,7 +2649,7 @@ private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configura private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { exists(TypedContent head | apa.pop(head) = result and - flowConsCand(head, result, config) + Stage4::consCand(head, result, config) ) } @@ -2323,7 +2723,7 @@ private newtype TAccessPath = private newtype TPathNode = TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { // A PathNode is introduced by a source ... - flow(node, config) and + Stage4::revFlow(node, config) and config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and @@ -2333,12 +2733,12 @@ private newtype TPathNode = exists(PathNodeMid mid | pathStep(mid, node, cc, sc, ap) and config = mid.getConfiguration() and - flow(node, _, _, ap.getApprox(), unbind(config)) + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) ) } or TPathNodeSink(Node node, Configuration config) { config.isSink(node) and - flow(node, unbind(config)) and + Stage4::revFlow(node, unbind(config)) and ( // A sink that is also a source ... config.isSource(node) @@ -2467,7 +2867,7 @@ private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { override TypedContent getHead() { result = head1 } override AccessPath getTail() { - flowConsCand(head1, result.getApprox(), _) and + Stage4::consCand(head1, result.getApprox(), _) and result.getHead() = head2 and result.length() = len - 1 } @@ -2498,7 +2898,7 @@ private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { override TypedContent getHead() { result = head } override AccessPath getTail() { - flowConsCand(head, result.getApprox(), _) and result.length() = len - 1 + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 } override AccessPathFrontHead getFront() { result = TFrontHead(head) } @@ -2732,33 +3132,22 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } -pragma[nomagic] -private predicate readCand(Node node1, TypedContent tc, Node node2, Configuration config) { - readCandFwd(node1, tc, _, node2, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathReadStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - readCand(mid.getNode(), tc, node, mid.getConfiguration()) and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and cc = mid.getCallContext() } -pragma[nomagic] -private predicate storeCand(Node node1, TypedContent tc, Node node2, Configuration config) { - storeCand2(node1, tc, node2, _, config) and - flow(node2, config) -} - pragma[nomagic] private predicate pathStoreStep( PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc ) { ap0 = mid.getAp() and - storeCand(mid.getNode(), tc, node, mid.getConfiguration()) and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and cc = mid.getCallContext() } @@ -2793,7 +3182,7 @@ private Node getAnOutNodeFlow( ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config ) { result = kind.getAnOutNode(call) and - flow(result, _, _, apa, config) + Stage4::revFlow(result, _, _, apa, config) } /** @@ -2829,7 +3218,7 @@ private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { exists(ParameterNode p | - flow(p, _, _, apa, config) and + Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) } @@ -2939,6 +3328,49 @@ predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = 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 + tuples = count(PathNode pn | reach(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 tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + private module FlowExploration { private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { exists(Node node1, Node node2 | diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll index efde95bd16a..28e53dfe7b5 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll @@ -802,14 +802,12 @@ abstract class AccessPathFront extends TAccessPathFront { abstract boolean toBoolNonEmpty(); + TypedContent getHead() { this = TFrontHead(result) } + + // TODO: delete predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { - exists(TypedContent tc | - this.headUsesContent(tc) and - clearsContent(n, tc.getContent()) - ) - } + predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { From 9e45f10c5ddb163efc4ea6d0419d72fd42ad434a Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 13 Nov 2020 15:12:39 +0100 Subject: [PATCH 50/97] Dataflow: Remove headUsesContent. --- .../semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll | 3 --- .../code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll | 3 --- .../code/csharp/dataflow/internal/DataFlowImplCommon.qll | 3 --- .../semmle/code/java/dataflow/internal/DataFlowImplCommon.qll | 3 --- .../semmle/python/dataflow/new/internal/DataFlowImplCommon.qll | 3 --- 5 files changed, 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll index 28e53dfe7b5..1d2e9052842 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll @@ -804,9 +804,6 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - // TODO: delete - predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll index 28e53dfe7b5..1d2e9052842 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll @@ -804,9 +804,6 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - // TODO: delete - predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll index 28e53dfe7b5..1d2e9052842 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll @@ -804,9 +804,6 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - // TODO: delete - predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index 28e53dfe7b5..1d2e9052842 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -804,9 +804,6 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - // TODO: delete - predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll index 28e53dfe7b5..1d2e9052842 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll @@ -804,9 +804,6 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - // TODO: delete - predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } } From 3dbd48063c435897310fd1d9e8f420a6237c10fa Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 16 Nov 2020 09:02:44 +0100 Subject: [PATCH 51/97] Dataflow: Add Unit type for all languages. --- .../code/cpp/dataflow/internal/DataFlowPrivate.qll | 9 +++++++++ .../code/cpp/ir/dataflow/internal/DataFlowPrivate.qll | 9 +++++++++ .../code/csharp/dataflow/internal/DataFlowPrivate.qll | 9 +++++++++ .../python/dataflow/new/internal/DataFlowPrivate.qll | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index 4562eb3fd19..da20528760f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -280,6 +280,15 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub impl int accessPathLimit() { result = 5 } +/** The unit type. */ +private newtype TUnit = TMkUnit() + +/** The trivial type with a single element. */ +class Unit extends TUnit { + /** Gets a textual representation of this element. */ + string toString() { result = "unit" } +} + /** * Holds if `n` does not require a `PostUpdateNode` as it either cannot be * modified or its modification cannot be observed, for example if it is a diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index e780c5c7eb3..3b55c2db3ce 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -510,6 +510,15 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub impl int accessPathLimit() { result = 5 } +/** The unit type. */ +private newtype TUnit = TMkUnit() + +/** The trivial type with a single element. */ +class Unit extends TUnit { + /** Gets a textual representation of this element. */ + string toString() { result = "unit" } +} + /** * Holds if `n` does not require a `PostUpdateNode` as it either cannot be * modified or its modification cannot be observed, for example if it is a diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll index bf5e6f1cfe6..f5bd357b876 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll @@ -1932,6 +1932,15 @@ private predicate viableConstantBooleanParamArg( int accessPathLimit() { result = 5 } +/** The unit type. */ +private newtype TUnit = TMkUnit() + +/** The trivial type with a single element. */ +class Unit extends TUnit { + /** Gets a textual representation of this element. */ + string toString() { result = "unit" } +} + /** * Holds if `n` does not require a `PostUpdateNode` as it either cannot be * modified or its modification cannot be observed, for example if it is a diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index 5ec17b71df1..c8109a1813d 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -1150,5 +1150,14 @@ predicate isImmutableOrUnobservable(Node n) { none() } int accessPathLimit() { result = 5 } +/** The unit type. */ +private newtype TUnit = TMkUnit() + +/** The trivial type with a single element. */ +class Unit extends TUnit { + /** Gets a textual representation of this element. */ + string toString() { result = "unit" } +} + /** Holds if `n` should be hidden from path explanations. */ predicate nodeIsHidden(Node n) { none() } From f74fc0ff269068be68f9a3269747fcacfad147f6 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 17 Nov 2020 14:28:25 +0100 Subject: [PATCH 52/97] Dataflow: Fix bad join-orders. --- cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll | 4 ++-- .../src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll | 4 ++-- .../src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll | 4 ++-- .../src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll | 4 ++-- .../semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll | 4 ++-- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll | 4 ++-- .../semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll | 4 ++-- .../semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll | 4 ++-- .../semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll | 4 ++-- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll | 4 ++-- .../semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll | 4 ++-- .../semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll | 4 ++-- .../semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll | 4 ++-- .../semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll | 4 ++-- .../src/semmle/code/java/dataflow/internal/DataFlowImpl.qll | 4 ++-- .../src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll | 4 ++-- .../src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll | 4 ++-- .../src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll | 4 ++-- .../src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll | 4 ++-- .../src/semmle/python/dataflow/new/internal/DataFlowImpl.qll | 4 ++-- .../src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll | 4 ++-- .../src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll | 4 ++-- .../src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll | 4 ++-- 23 files changed, 46 insertions(+), 46 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll index 49a02e9d7ad..136c55f9e5f 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll @@ -1189,7 +1189,7 @@ private module Stage2 { exists(Ap ap1, Ap ap2 | revFlow(node2, _, _, ap2, config) and readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) ) } @@ -1551,7 +1551,7 @@ private module Stage3 { ) { exists(DataFlowType contentType | fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and typecheckStore(ap1, contentType) ) } From 8ffcff0824fd18dc30e21ad9596c6e48d1bc2ee6 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 11 Nov 2020 15:55:25 +0100 Subject: [PATCH 53/97] Python: Add example of top-level module shadowing stdlib Although this test is added under the `wrong` folder, the current results from this CodeQL test is actually correct (compared with the Python interpreter). However, they don't match what the extractor does when invoked with `codeql database create`. Since I deemed it "more than an easy fix" to change the extractor behavior for `codeql database create` to match the real python behavior, and it turned out to be quite a challenge to change the extractor behavior for all tests, I'm just going to make THIS ONE test-case behave like the extractor will with `codeql database create`... This is a first commit, to show how the extractor works with qltest by default. Inspired by the debugging in https://github.com/github/codeql/issues/4640 --- .../LocalModuleWithRef.expected | 3 +++ .../conflict-stdlib/LocalModuleWithRef.ql | 13 +++++++++++++ .../conflict-stdlib/LocalModules.expected | 5 +++++ .../conflict-stdlib/LocalModules.ql | 5 +++++ .../ModuleWithLocalRef.expected | 3 +++ .../conflict-stdlib/ModuleWithLocalRef.ql | 19 +++++++++++++++++++ .../module-imports/conflict-stdlib/README.md | 8 ++++++++ .../module-imports/conflict-stdlib/cmd.py | 2 ++ .../module-imports/conflict-stdlib/options | 1 + .../conflict-stdlib/test_fail.py | 3 +++ .../module-imports/conflict-stdlib/test_ok.py | 2 ++ .../conflict-stdlib/unique_name.py | 1 + .../conflict-stdlib/unique_name_use.py | 2 ++ 13 files changed, 67 insertions(+) create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.ql create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.ql create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.ql create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py create mode 100644 python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected new file mode 100644 index 00000000000..d5c1329b657 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected @@ -0,0 +1,3 @@ +| Local module | cmd.py:0:0:0:0 | Module cmd | referenced in external file called | pdb.py | +| Local module | cmd.py:0:0:0:0 | Module cmd | referenced in local file called | test_ok.py | +| Local module | unique_name.py:0:0:0:0 | Module unique_name | referenced in local file called | unique_name_use.py | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.ql b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.ql new file mode 100644 index 00000000000..5bdb99415b2 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.ql @@ -0,0 +1,13 @@ +import python + +from ModuleValue mv, ControlFlowNode ref, string local_external +where + ref = mv.getAReference() and + exists(mv.getScope().getFile().getRelativePath()) and + ( + if exists(ref.getLocation().getFile().getRelativePath()) + then local_external = "local" + else local_external = "external" + ) +select "Local module", mv, "referenced in " + local_external + " file called", + ref.getLocation().getFile().getShortName() diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected new file mode 100644 index 00000000000..ffed6f7edc7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected @@ -0,0 +1,5 @@ +| cmd.py:0:0:0:0 | Module cmd | +| test_fail.py:0:0:0:0 | Module test_fail | +| test_ok.py:0:0:0:0 | Module test_ok | +| unique_name.py:0:0:0:0 | Module unique_name | +| unique_name_use.py:0:0:0:0 | Module unique_name_use | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.ql b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.ql new file mode 100644 index 00000000000..faacf90522d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.ql @@ -0,0 +1,5 @@ +import python + +from Module m +where exists(m.getFile().getRelativePath()) +select m diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected new file mode 100644 index 00000000000..c2c230b25f9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected @@ -0,0 +1,3 @@ +| Module 'cmd' (local, not in stdlib, not missing) referenced in local file | test_ok.py:1 | +| Module 'pdb' (external, in stdlib, not missing) referenced in local file | test_fail.py:3 | +| Module 'unique_name' (local, not in stdlib, not missing) referenced in local file | unique_name_use.py:1 | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.ql b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.ql new file mode 100644 index 00000000000..030211ba0bf --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.ql @@ -0,0 +1,19 @@ +import python + +from ModuleValue mv, ControlFlowNode ref, string in_stdlib, string local_external, string is_missing +where + ref = mv.getAReference() and + exists(ref.getLocation().getFile().getRelativePath()) and + ( + if mv.getScope().getFile().inStdlib() + then in_stdlib = "in stdlib" + else in_stdlib = "not in stdlib" + ) and + ( + if exists(mv.getScope().getFile().getRelativePath()) + then local_external = "local" + else local_external = "external" + ) and + (if mv.isAbsent() then is_missing = "missing" else is_missing = "not missing") +select "Module '" + mv.getName() + "' (" + local_external + ", " + in_stdlib + ", " + is_missing + + ") referenced in local file", ref.getLocation().toString() diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md new file mode 100644 index 00000000000..cd607bc0cf7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md @@ -0,0 +1,8 @@ +This test shows how we handle modules the shadow a module in the standard library. + +We manually replicate the behavior of `codeql database create --source-root `, which will use `-R `. By default, the way qltest invokes the extractor will cause different behavior. Therefore, we also need to move our code outside of the top-level folder, and it lives in `code/`. + +Because we have a `cmd.py` file, whenever the python interpreter sees `import cmd`, that is the file that will be used! -- + +* `python test_ok.py` works as intended, and prints `Foo` +* `python test_fail.py` raises an exception, since it imports `pdb.py` from the standard library, which (at least in Python 3.8) tries to import `cmd.py` from the standard library, but instead is served our `cmd.py` module. Therefore it fails with `AttributeError: module 'cmd' has no attribute 'Cmd'` diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py new file mode 100644 index 00000000000..58bbb12f69c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py @@ -0,0 +1,2 @@ +foo = "Foo" +print("my own cmd imported") diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options new file mode 100644 index 00000000000..b91afde0767 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py new file mode 100644 index 00000000000..f9621b26093 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py @@ -0,0 +1,3 @@ +# we import `pdb` which import the `cmd` module from the standard library +# and allows us to set --max-import-depth=2, to make the test run fast +import pdb diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py new file mode 100644 index 00000000000..6dcf8eb614e --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py @@ -0,0 +1,2 @@ +from cmd import foo +print(foo) diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py new file mode 100644 index 00000000000..191d0d6e419 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py @@ -0,0 +1 @@ +foo = "Foo" diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py new file mode 100644 index 00000000000..79d2ca3c350 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py @@ -0,0 +1,2 @@ +from unique_name import foo +print(foo) From 7e407d43d229de6a3cba3bcd94d442d1e0ba8b2c Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 19 Nov 2020 13:31:57 +0100 Subject: [PATCH 54/97] Python: Change (single) test to match `codeql database create` --- .../conflict-stdlib/LocalModuleWithRef.expected | 3 --- .../conflict-stdlib/LocalModules.expected | 10 +++++----- .../conflict-stdlib/ModuleWithLocalRef.expected | 6 +++--- .../wrong/module-imports/conflict-stdlib/README.md | 6 ++++-- .../{ => code-invalid-package-name}/cmd.py | 0 .../{ => code-invalid-package-name}/test_fail.py | 0 .../{ => code-invalid-package-name}/test_ok.py | 0 .../{ => code-invalid-package-name}/unique_name.py | 0 .../{ => code-invalid-package-name}/unique_name_use.py | 0 .../wrong/module-imports/conflict-stdlib/options | 2 +- 10 files changed, 13 insertions(+), 14 deletions(-) rename python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/{ => code-invalid-package-name}/cmd.py (100%) rename python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/{ => code-invalid-package-name}/test_fail.py (100%) rename python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/{ => code-invalid-package-name}/test_ok.py (100%) rename python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/{ => code-invalid-package-name}/unique_name.py (100%) rename python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/{ => code-invalid-package-name}/unique_name_use.py (100%) diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected index d5c1329b657..e69de29bb2d 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModuleWithRef.expected @@ -1,3 +0,0 @@ -| Local module | cmd.py:0:0:0:0 | Module cmd | referenced in external file called | pdb.py | -| Local module | cmd.py:0:0:0:0 | Module cmd | referenced in local file called | test_ok.py | -| Local module | unique_name.py:0:0:0:0 | Module unique_name | referenced in local file called | unique_name_use.py | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected index ffed6f7edc7..7d18240655f 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/LocalModules.expected @@ -1,5 +1,5 @@ -| cmd.py:0:0:0:0 | Module cmd | -| test_fail.py:0:0:0:0 | Module test_fail | -| test_ok.py:0:0:0:0 | Module test_ok | -| unique_name.py:0:0:0:0 | Module unique_name | -| unique_name_use.py:0:0:0:0 | Module unique_name_use | +| code-invalid-package-name/cmd.py:0:0:0:0 | Script cmd.py | +| code-invalid-package-name/test_fail.py:0:0:0:0 | Script test_fail.py | +| code-invalid-package-name/test_ok.py:0:0:0:0 | Script test_ok.py | +| code-invalid-package-name/unique_name.py:0:0:0:0 | Script unique_name.py | +| code-invalid-package-name/unique_name_use.py:0:0:0:0 | Script unique_name_use.py | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected index c2c230b25f9..2c66cfdbc99 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/ModuleWithLocalRef.expected @@ -1,3 +1,3 @@ -| Module 'cmd' (local, not in stdlib, not missing) referenced in local file | test_ok.py:1 | -| Module 'pdb' (external, in stdlib, not missing) referenced in local file | test_fail.py:3 | -| Module 'unique_name' (local, not in stdlib, not missing) referenced in local file | unique_name_use.py:1 | +| Module 'cmd' (external, in stdlib, not missing) referenced in local file | code-invalid-package-name/test_ok.py:1 | +| Module 'pdb' (external, in stdlib, not missing) referenced in local file | code-invalid-package-name/test_fail.py:3 | +| Module 'unique_name' (external, not in stdlib, missing) referenced in local file | code-invalid-package-name/unique_name_use.py:1 | diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md index cd607bc0cf7..6c80c3976dc 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md @@ -1,8 +1,10 @@ This test shows how we handle modules the shadow a module in the standard library. -We manually replicate the behavior of `codeql database create --source-root `, which will use `-R `. By default, the way qltest invokes the extractor will cause different behavior. Therefore, we also need to move our code outside of the top-level folder, and it lives in `code/`. +We manually replicate the behavior of `codeql database create --source-root `, which will use `-R `. By default, the way qltest invokes the extractor will cause different behavior. Therefore, we also need to move our code outside of the top-level folder, and it lives in `code-invalid-package-name/` -- notice that if we use `code` as the folder name, the extractor will treat it as if there is a package called `code` (note, `codeql database create` would not the folder `code` as a package when `code` is used as the `--source-root`). -Because we have a `cmd.py` file, whenever the python interpreter sees `import cmd`, that is the file that will be used! -- +The results from `LocalModules.ql`, where everything is a script, matches with the extractor :+1: + +Because we have a `cmd.py` file, whenever the python interpreter sees `import cmd`, that is the file that will be used! * `python test_ok.py` works as intended, and prints `Foo` * `python test_fail.py` raises an exception, since it imports `pdb.py` from the standard library, which (at least in Python 3.8) tries to import `cmd.py` from the standard library, but instead is served our `cmd.py` module. Therefore it fails with `AttributeError: module 'cmd' has no attribute 'Cmd'` diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/cmd.py similarity index 100% rename from python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/cmd.py rename to python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/cmd.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/test_fail.py similarity index 100% rename from python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_fail.py rename to python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/test_fail.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/test_ok.py similarity index 100% rename from python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/test_ok.py rename to python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/test_ok.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/unique_name.py similarity index 100% rename from python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name.py rename to python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/unique_name.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/unique_name_use.py similarity index 100% rename from python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/unique_name_use.py rename to python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/code-invalid-package-name/unique_name_use.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options index b91afde0767..ee0f5414146 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/options @@ -1 +1 @@ -semmle-extractor-options: --max-import-depth=2 +semmle-extractor-options: --max-import-depth=2 -R code-invalid-package-name/ From c571e42cd5a8f09c1255612d703e5d814e2f7426 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 11 Nov 2020 14:18:31 +0100 Subject: [PATCH 55/97] C#: Move internal CFG logic into separate file --- .../semmle/code/csharp/commons/Assertions.qll | 3 +- .../csharp/controlflow/ControlFlowElement.qll | 7 +- .../csharp/controlflow/ControlFlowGraph.qll | 1668 +---------------- .../internal/ControlFlowGraphImpl.qll | 1642 ++++++++++++++++ .../controlflow/internal/NonReturning.qll | 2 +- .../controlflow/internal/PreBasicBlocks.qll | 4 +- .../csharp/controlflow/internal/PreSsa.qll | 4 +- .../csharp/controlflow/internal/Splitting.qll | 8 +- .../controlflow/internal/SuccessorType.qll | 2 +- .../controlflow/graph/Consistency.ql | 3 +- .../controlflow/graph/EntryElement.ql | 2 +- .../controlflow/graph/ExitElement.ql | 2 +- .../library-tests/controlflow/graph/Nodes.ql | 3 +- .../dataflow/ssa/PreSsaConsistency.ql | 2 +- 14 files changed, 1667 insertions(+), 1685 deletions(-) create mode 100644 csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll diff --git a/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll b/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll index 77fa61a3efa..d425ec118ed 100644 --- a/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll +++ b/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll @@ -1,5 +1,6 @@ /** Provides classes for assertions. */ +private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl private import semmle.code.csharp.frameworks.system.Diagnostics private import semmle.code.csharp.frameworks.system.diagnostics.Contracts private import semmle.code.csharp.frameworks.test.VisualStudio @@ -189,7 +190,7 @@ class Assertion extends MethodCall { deprecated private predicate immediatelyDominatesBlockSplit(BasicBlock succ) { // Only calculate dominance by explicit recursion for split nodes; // all other nodes can use regular CFG dominance - this instanceof ControlFlow::Internal::SplitControlFlowElement and + this instanceof SplitControlFlowElement and exists(BasicBlock bb | bb.getANode() = this.getAControlFlowNode() | succ = bb.getASuccessor() and forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != bb | diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll index 7f0a3666b29..dcae798813a 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll @@ -6,6 +6,7 @@ private import ControlFlow private import ControlFlow::BasicBlocks private import SuccessorTypes private import semmle.code.csharp.Caching +private import internal.ControlFlowGraphImpl /** * A program element that can possess control flow. That is, either a statement or @@ -38,14 +39,14 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { * Gets a first control flow node executed within this element. */ Nodes::ElementNode getAControlFlowEntryNode() { - result = Internal::getAControlFlowEntryNode(this).getAControlFlowNode() + result = getAControlFlowEntryNode(this).getAControlFlowNode() } /** * Gets a potential last control flow node executed within this element. */ Nodes::ElementNode getAControlFlowExitNode() { - result = Internal::getAControlFlowExitNode(this).getAControlFlowNode() + result = getAControlFlowExitNode(this).getAControlFlowNode() } /** @@ -80,7 +81,7 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { ) { // Only calculate dominance by explicit recursion for split nodes; // all other nodes can use regular CFG dominance - this instanceof ControlFlow::Internal::SplitControlFlowElement and + this instanceof SplitControlFlowElement and cb.getLastNode() = this.getAControlFlowNode() and succ = cb.getASuccessorByType(s) } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index 78593c914af..5fe2f2e5b83 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -5,9 +5,10 @@ import csharp */ module ControlFlow { private import semmle.code.csharp.controlflow.BasicBlocks as BBs - private import semmle.code.csharp.controlflow.internal.Completion import semmle.code.csharp.controlflow.internal.SuccessorType private import SuccessorTypes + private import internal.ControlFlowGraphImpl + private import internal.Splitting /** * A control flow node. @@ -385,1669 +386,4 @@ module ControlFlow { class ConditionBlock = BBs::ConditionBlock; } - - /** - * INTERNAL: Do not use. - */ - module Internal { - import semmle.code.csharp.controlflow.internal.Splitting - - /** - * Provides auxiliary classes and predicates used to construct the basic successor - * relation on control flow elements. - * - * The implementation is centered around the concept of a _completion_, which - * models how the execution of a statement or expression terminates. - * Completions are represented as an algebraic data type `Completion` defined in - * `Completion.qll`. - * - * The CFG is built by structural recursion over the AST. To achieve this the - * CFG edges related to a given AST node, `n`, are divided into three categories: - * - * 1. The in-going edge that points to the first CFG node to execute when - * `n` is going to be executed. - * 2. The out-going edges for control flow leaving `n` that are going to some - * other node in the surrounding context of `n`. - * 3. The edges that have both of their end-points entirely within the AST - * node and its children. - * - * The edges in (1) and (2) are inherently non-local and are therefore - * initially calculated as half-edges, that is, the single node, `k`, of the - * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`, - * respectively. The edges in (3) can then be enumerated directly by the predicate - * `succ` by calling `first` and `last` recursively on the children of `n` and - * connecting the end-points. This yields the entire CFG, since all edges are in - * (3) for _some_ AST node. - * - * The second parameter of `last` is the completion, which is necessary to distinguish - * the out-going edges from `n`. Note that the completion changes as the calculation of - * `last` proceeds outward through the AST; for example, a `BreakCompletion` is - * caught up by its surrounding loop and turned into a `NormalCompletion`, and a - * `NormalCompletion` proceeds outward through the end of a `finally` block and is - * turned into whatever completion was caught by the `finally`. - * - * An important goal of the CFG is to get the order of side-effects correct. - * Most expressions can have side-effects and must therefore be modeled in the - * CFG in AST post-order. For example, a `MethodCall` evaluates its arguments - * before the call. Most statements do not have side-effects, but merely affect - * the control flow and some could therefore be excluded from the CFG. However, - * as a design choice, all statements are included in the CFG and generally - * serve as their own entry-points, thus executing in some version of AST - * pre-order. - */ - module Successor { - private import semmle.code.csharp.ExprOrStmtParent - - /** - * A control flow element where the children are evaluated following a - * standard left-to-right evaluation. The actual evaluation order is - * determined by the predicate `getChildElement()`. - */ - abstract private class StandardElement extends ControlFlowElement { - /** Gets the first child element of this element. */ - ControlFlowElement getFirstChildElement() { result = this.getChildElement(0) } - - /** Holds if this element has no children. */ - predicate isLeafElement() { not exists(this.getFirstChildElement()) } - - /** Gets the last child element of this element. */ - ControlFlowElement getLastChildElement() { - exists(int last | - last = max(int i | exists(this.getChildElement(i))) and - result = this.getChildElement(last) - ) - } - - /** Gets the `i`th child element, which is not the last element. */ - pragma[noinline] - ControlFlowElement getNonLastChildElement(int i) { - result = this.getChildElement(i) and - not result = this.getLastChildElement() - } - - /** Gets the `i`th child element, in order of evaluation, starting from 0. */ - abstract ControlFlowElement getChildElement(int i); - } - - private class StandardStmt extends StandardElement, Stmt { - StandardStmt() { - // The following statements need special treatment - not this instanceof IfStmt and - not this instanceof SwitchStmt and - not this instanceof CaseStmt and - not this instanceof LoopStmt and - not this instanceof TryStmt and - not this instanceof SpecificCatchClause and - not this instanceof JumpStmt - } - - override ControlFlowElement getChildElement(int i) { - not this instanceof GeneralCatchClause and - not this instanceof FixedStmt and - not this instanceof UsingBlockStmt and - result = this.getChild(i) - or - this = any(GeneralCatchClause gcc | i = 0 and result = gcc.getBlock()) - or - this = - any(FixedStmt fs | - result = fs.getVariableDeclExpr(i) - or - result = fs.getBody() and - i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 - ) - or - this = - any(UsingBlockStmt us | - if exists(us.getExpr()) - then ( - result = us.getExpr() and - i = 0 - or - result = us.getBody() and - i = 1 - ) else ( - result = us.getVariableDeclExpr(i) - or - result = us.getBody() and - i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 - ) - ) - } - } - - /** - * An assignment operation that has an expanded version. We use the expanded - * version in the control flow graph in order to get better data flow / taint - * tracking. - */ - private class AssignOperationWithExpandedAssignment extends AssignOperation { - AssignOperationWithExpandedAssignment() { this.hasExpandedAssignment() } - } - - /** A conditionally qualified expression. */ - private class ConditionallyQualifiedExpr extends QualifiableExpr { - ConditionallyQualifiedExpr() { this.isConditional() } - } - - /** An expression that should not be included in the control flow graph. */ - abstract private class NoNodeExpr extends Expr { } - - private class SimpleNoNodeExpr extends NoNodeExpr { - SimpleNoNodeExpr() { - this instanceof TypeAccess and - not this = any(PatternMatch pm).getPattern() - } - } - - /** A write access that is not also a read access. */ - private class WriteAccess extends AssignableWrite { - WriteAccess() { - // `x++` is both a read and write access - not this instanceof AssignableRead - } - } - - private class WriteAccessNoNodeExpr extends WriteAccess, NoNodeExpr { - WriteAccessNoNodeExpr() { - // For example a write to a static field, `Foo.Bar = 0`. - forall(Expr e | e = this.getAChildExpr() | e instanceof NoNodeExpr) - } - } - - private ControlFlowElement getExprChildElement0(Expr e, int i) { - not e instanceof NameOfExpr and - not e instanceof QualifiableExpr and - not e instanceof Assignment and - not e instanceof AnonymousFunctionExpr and - result = e.getChild(i) - or - e = any(ExtensionMethodCall emc | result = emc.getArgument(i)) - or - e = - any(QualifiableExpr qe | - not qe instanceof ExtensionMethodCall and - result = qe.getChild(i) - ) - or - e = - any(Assignment a | - // The left-hand side of an assignment is evaluated before the right-hand side - i = 0 and result = a.getLValue() - or - i = 1 and result = a.getRValue() - ) - } - - private ControlFlowElement getExprChildElement(Expr e, int i) { - result = - rank[i + 1](ControlFlowElement cfe, int j | - cfe = getExprChildElement0(e, j) and - not cfe instanceof NoNodeExpr - | - cfe order by j - ) - } - - private int getFirstChildElement(Expr e) { - result = min(int i | exists(getExprChildElement(e, i))) - } - - private int getLastChildElement(Expr e) { - result = max(int i | exists(getExprChildElement(e, i))) - } - - /** - * A qualified write access. In a qualified write access, the access itself is - * not evaluated, only the qualifier and the indexer arguments (if any). - */ - private class QualifiedWriteAccess extends WriteAccess, QualifiableExpr { - QualifiedWriteAccess() { - this.hasQualifier() - or - // Member initializers like - // ```csharp - // new Dictionary() { [0] = "Zero", [1] = "One", [2] = "Two" } - // ``` - // need special treatment, because the the accesses `[0]`, `[1]`, and `[2]` - // have no qualifier. - this = any(MemberInitializer mi).getLValue() - } - } - - /** A normal or a (potential) dynamic call to an accessor. */ - private class StatOrDynAccessorCall extends Expr { - StatOrDynAccessorCall() { - this instanceof AccessorCall or - this instanceof DynamicAccess - } - } - - /** - * An expression that writes via an accessor call, for example `x.Prop = 0`, - * where `Prop` is a property. - * - * Accessor writes need special attention, because we need to model the fact - * that the accessor is called *after* the assigned value has been evaluated. - * In the example above, this means we want a CFG that looks like - * - * ```csharp - * x -> 0 -> set_Prop -> x.Prop = 0 - * ``` - */ - class AccessorWrite extends Expr { - AssignableDefinition def; - - AccessorWrite() { - def.getExpr() = this and - def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and - not this instanceof AssignOperationWithExpandedAssignment - } - - /** - * Gets the `i`th accessor being called in this write. More than one call - * can happen in tuple assignments. - */ - StatOrDynAccessorCall getCall(int i) { - result = - rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef | - tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall - | - tdef order by tdef.getEvaluationOrder() - ).getTargetAccess() - or - i = 0 and - result = def.getTargetAccess() and - not def instanceof AssignableDefinitions::TupleAssignmentDefinition - } - } - - private class StandardExpr extends StandardElement, Expr { - StandardExpr() { - // The following expressions need special treatment - not this instanceof LogicalNotExpr and - not this instanceof LogicalAndExpr and - not this instanceof LogicalOrExpr and - not this instanceof NullCoalescingExpr and - not this instanceof ConditionalExpr and - not this instanceof AssignOperationWithExpandedAssignment and - not this instanceof ConditionallyQualifiedExpr and - not this instanceof ThrowExpr and - not this instanceof ObjectCreation and - not this instanceof ArrayCreation and - not this instanceof QualifiedWriteAccess and - not this instanceof AccessorWrite and - not this instanceof NoNodeExpr and - not this instanceof SwitchExpr and - not this instanceof SwitchCaseExpr - } - - override ControlFlowElement getChildElement(int i) { result = getExprChildElement(this, i) } - } - - /** - * Gets the first element executed within control flow element `cfe`. - */ - ControlFlowElement first(ControlFlowElement cfe) { - // Pre-order: element itself - cfe instanceof PreOrderElement and - result = cfe - or - // Post-order: first element of first child (or self, if no children) - cfe = - any(PostOrderElement poe | - result = first(poe.getFirstChild()) - or - not exists(poe.getFirstChild()) and - result = poe - ) - or - cfe = - any(AssignOperationWithExpandedAssignment a | result = first(a.getExpandedAssignment())) - or - cfe = any(ConditionallyQualifiedExpr cqe | result = first(getExprChildElement(cqe, 0))) - or - cfe = - any(ObjectCreation oc | - result = first(getObjectCreationArgument(oc, 0)) - or - not exists(getObjectCreationArgument(oc, 0)) and - result = oc - ) - or - cfe = - any(ArrayCreation ac | - // First element of first length argument - result = first(ac.getLengthArgument(0)) - or - // No length argument: element itself - not exists(ac.getLengthArgument(0)) and - result = ac - ) - or - cfe = - any(ForeachStmt fs | - // Unlike most other statements, `foreach` statements are not modelled in - // pre-order, because we use the `foreach` node itself to represent the - // emptiness test that determines whether to execute the loop body - result = first(fs.getIterableExpr()) - ) - or - cfe instanceof QualifiedWriteAccess and - result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) - or - cfe instanceof AccessorWrite and - result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) - } - - private class PreOrderElement extends Stmt { - PreOrderElement() { - this instanceof StandardStmt - or - this instanceof IfStmt - or - this instanceof SwitchStmt - or - this instanceof CaseStmt - or - this instanceof TryStmt - or - this instanceof SpecificCatchClause - or - this instanceof LoopStmt and not this instanceof ForeachStmt - } - } - - private Expr getObjectCreationArgument(ObjectCreation oc, int i) { - i >= 0 and - if oc.hasInitializer() - then result = getExprChildElement(oc, i + 1) - else result = getExprChildElement(oc, i) - } - - private class PostOrderElement extends ControlFlowElement { - PostOrderElement() { - this instanceof StandardExpr - or - this instanceof LogicalNotExpr - or - this instanceof LogicalAndExpr - or - this instanceof LogicalOrExpr - or - this instanceof NullCoalescingExpr - or - this instanceof ConditionalExpr - or - this instanceof SwitchExpr - or - this instanceof SwitchCaseExpr - or - this instanceof JumpStmt - or - this instanceof ThrowExpr - } - - ControlFlowElement getFirstChild() { - result = this.(StandardExpr).getFirstChildElement() - or - result = this.(LogicalNotExpr).getOperand() - or - result = this.(LogicalAndExpr).getLeftOperand() - or - result = this.(LogicalOrExpr).getLeftOperand() - or - result = this.(NullCoalescingExpr).getLeftOperand() - or - result = this.(ConditionalExpr).getCondition() - or - result = this.(SwitchExpr).getExpr() - or - result = this.(SwitchCaseExpr).getPattern() - or - result = this.(JumpStmt).getChild(0) - or - result = this.(ThrowExpr).getExpr() - } - } - - /** A specification of how to compute the last element of a control flow element. */ - private newtype TLastComputation = - /** The element is itself the last element. */ - TSelf(Completion c) or - /** The last element must be computed recursively. */ - TRec(TLastRecComputation c) - - /** - * A specification of how to compute the last element of a control flow element - * using recursion. - */ - private newtype TLastRecComputation = - TLastRecSpecificCompletion(Completion c) or - TLastRecSpecificNegCompletion(Completion c) or - TLastRecAnyCompletion() or - TLastRecNormalCompletion() or - TLastRecAbnormalCompletion() or - TLastRecBreakCompletion() or - TLastRecSwitchAbnormalCompletion() or - TLastRecInvalidOperationException() or - TLastRecNonContinueCompletion() or - TLastRecLoopBodyAbnormal() - - private TSelf getValidSelfCompletion(ControlFlowElement cfe) { - result = TSelf(any(Completion c | c.isValidFor(cfe))) - } - - private TRec specificBoolean(boolean value) { - result = TRec(TLastRecSpecificCompletion(any(BooleanCompletion bc | bc.getValue() = value))) - } - - /** - * Gets an element from which the last element of `cfe` can be computed - * (recursively) based on computation specification `c`. The predicate - * itself is non-recursive. - * - * With the exception of `try` statements, all elements have a simple - * recursive last computation. - */ - pragma[nomagic] - private ControlFlowElement lastNonRec(ControlFlowElement cfe, TLastComputation c) { - // Pre-order: last element of last child (or self, if no children) - cfe = - any(StandardStmt ss | - result = ss.getLastChildElement() and - c = TRec(TLastRecAnyCompletion()) - or - ss.isLeafElement() and - result = ss and - c = getValidSelfCompletion(result) - ) - or - // Post-order: element itself - cfe instanceof PostOrderElement and - result = cfe and - c = getValidSelfCompletion(result) - or - // Pre/post order: a child exits abnormally - result = cfe.(StandardElement).getChildElement(_) and - c = TRec(TLastRecAbnormalCompletion()) - or - // Operand exits abnormally - result = cfe.(LogicalNotExpr).getOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(LogicalAndExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(LogicalOrExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(NullCoalescingExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(ConditionalExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - cfe = - any(AssignOperation ao | - result = ao.getExpandedAssignment() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(ConditionallyQualifiedExpr cqe | - // Post-order: element itself - result = cqe and - c = getValidSelfCompletion(result) - or - // Qualifier exits with a `null` completion - result = getExprChildElement(cqe, 0) and - c = TRec(TLastRecSpecificCompletion(any(NullnessCompletion nc | nc.isNull()))) - ) - or - // Expression being thrown exits abnormally - result = cfe.(ThrowExpr).getExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - cfe = - any(ObjectCreation oc | - // Post-order: element itself (when no initializer) - result = oc and - not oc.hasInitializer() and - c = getValidSelfCompletion(result) - or - // Last element of initializer - result = oc.getInitializer() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(ArrayCreation ac | - // Post-order: element itself (when no initializer) - result = ac and - not ac.hasInitializer() and - c = getValidSelfCompletion(result) - or - // Last element of initializer - result = ac.getInitializer() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(IfStmt is | - // Condition exits with a false completion and there is no `else` branch - result = is.getCondition() and - c = specificBoolean(false) and - not exists(is.getElse()) - or - // Condition exits abnormally - result = is.getCondition() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Then branch exits with any completion - result = is.getThen() and - c = TRec(TLastRecAnyCompletion()) - or - // Else branch exits with any completion - result = is.getElse() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(Switch s | - // Switch expression exits abnormally - result = s.getExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - // A case exits abnormally - result = s.getACase() and - c = TRec(TLastRecSwitchAbnormalCompletion()) - ) - or - cfe = - any(SwitchStmt ss | - // Switch expression exits normally and there are no cases - result = ss.getExpr() and - not exists(ss.getACase()) and - c = TRec(TLastRecNormalCompletion()) - or - // A statement exits with a `break` completion - result = ss.getStmt(_) and - c = TRec(TLastRecBreakCompletion()) - or - // A statement exits abnormally - result = ss.getStmt(_) and - c = TRec(TLastRecSwitchAbnormalCompletion()) - or - // Last case exits with a non-match - exists(CaseStmt cs, int last | - last = max(int i | exists(ss.getCase(i))) and - cs = ss.getCase(last) - | - result = cs.getPattern() and - c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) - or - result = cs.getCondition() and - c = specificBoolean(false) - ) - ) - or - cfe = - any(SwitchExpr se | - // Last case exists with a non-match - exists(SwitchCaseExpr sce, int i | - sce = se.getCase(i) and - not sce.matchesAll() and - not exists(se.getCase(i + 1)) and - c = TRec(TLastRecInvalidOperationException()) - | - result = sce.getPattern() or - result = sce.getCondition() - ) - ) - or - cfe = - any(Case case | - // Condition, pattern, or body exists abnormally - result in [case.getCondition(), case.getPattern(), case.getBody()] and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(CaseStmt case | - // Condition exists with a `false` completion - result = case.getCondition() and - c = specificBoolean(false) - or - // Case pattern exits with a non-match - result = case.getPattern() and - c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) - or - // Case body exits with any completion - result = case.getBody() and - c = TRec(TLastRecAnyCompletion()) - ) - or - exists(LoopStmt ls | - cfe = ls and - not ls instanceof ForeachStmt - | - // Condition exits with a false completion - result = ls.getCondition() and - c = specificBoolean(false) - or - // Condition exits abnormally - result = ls.getCondition() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - result = ls.getBody() and - c = TRec(TLastRecBreakCompletion()) - or - // Body exits with a completion that does not continue the loop - result = ls.getBody() and - c = TRec(TLastRecNonContinueCompletion()) - ) - or - cfe = - any(ForeachStmt fs | - // Iterator expression exits abnormally - result = fs.getIterableExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Emptiness test exits with no more elements - result = fs and - c = TSelf(any(EmptinessCompletion ec | ec.isEmpty())) - or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - result = fs.getBody() and - c = TRec(TLastRecBreakCompletion()) - or - // Body exits abnormally - result = fs.getBody() and - c = TRec(TLastRecLoopBodyAbnormal()) - ) - or - cfe = - any(TryStmt ts | - // If the `finally` block completes abnormally, take the completion of - // the `finally` block itself - result = ts.getFinally() and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(SpecificCatchClause scc | - // Last element of `catch` block - result = scc.getBlock() and - c = TRec(TLastRecAnyCompletion()) - or - not scc.isLast() and - ( - // Incompatible exception type: clause itself - result = scc and - c = TSelf(any(MatchingCompletion mc | mc.isNonMatch())) - or - // Incompatible filter - result = scc.getFilterClause() and - c = specificBoolean(false) - ) - ) - or - cfe = - any(JumpStmt js | - // Post-order: element itself - result = js and - c = getValidSelfCompletion(result) - or - // Child exits abnormally - result = js.getChild(0) and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(QualifiedWriteAccess qwa | - // Skip the access in a qualified write access - result = getExprChildElement(qwa, getLastChildElement(qwa)) and - c = TRec(TLastRecAnyCompletion()) - or - // A child exits abnormally - result = getExprChildElement(qwa, _) and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(AccessorWrite aw | - // Post-order: element itself - result = aw and - c = getValidSelfCompletion(result) - or - // A child exits abnormally - result = getExprChildElement(aw, _) and - c = TRec(TLastRecAbnormalCompletion()) - or - // An accessor call exits abnormally - result = aw.getCall(_) and - c = - TSelf(any(Completion comp | - comp.isValidFor(result) and not comp instanceof NormalCompletion - )) - ) - } - - pragma[noinline] - private LabeledStmt getLabledStmt(string label, Callable c) { - result.getEnclosingCallable() = c and - label = result.getLabel() - } - - /** - * Gets a potential last element executed within control flow element `cfe`, - * as well as its completion. - * - * For example, if `cfe` is `A || B` then both `A` and `B` are potential - * last elements with Boolean completions. - */ - ControlFlowElement last(ControlFlowElement cfe, Completion c) { - result = lastNonRec(cfe, TSelf(c)) - or - result = lastRecSpecific(cfe, c, c) - or - exists(TLastRecComputation rec, Completion c0 | result = lastRec(rec, cfe, c0) | - rec = TLastRecSpecificNegCompletion(any(Completion c1 | c1 != c0)) and - c = c0 - or - rec = TLastRecAnyCompletion() and c = c0 - or - rec = TLastRecNormalCompletion() and - c0 instanceof NormalCompletion and - c = c0 - or - rec = TLastRecAbnormalCompletion() and - not c0 instanceof NormalCompletion and - c = c0 - or - rec = TLastRecBreakCompletion() and - c0 instanceof BreakCompletion and - c instanceof BreakNormalCompletion - or - rec = TLastRecSwitchAbnormalCompletion() and - not c instanceof BreakCompletion and - not c instanceof NormalCompletion and - not getLabledStmt(c.(GotoCompletion).getLabel(), cfe.getEnclosingCallable()) instanceof - CaseStmt and - c = c0 - or - rec = TLastRecInvalidOperationException() and - (c0.(MatchingCompletion).isNonMatch() or c0 instanceof FalseCompletion) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = c0 and - nc - .getOuterCompletion() - .(ThrowCompletion) - .getExceptionClass() - .hasQualifiedName("System.InvalidOperationException") - ) - or - rec = TLastRecNonContinueCompletion() and - not c0 instanceof BreakCompletion and - not c0.continuesLoop() and - c = c0 - or - rec = TLastRecLoopBodyAbnormal() and - not c0 instanceof NormalCompletion and - not c0 instanceof ContinueCompletion and - not c0 instanceof BreakCompletion and - c = c0 - ) - or - // Last `catch` clause inherits throw completions from the `try` block, - // when the clause does not match - exists(SpecificCatchClause scc, ThrowCompletion tc | - scc = cfe and - scc.isLast() and - throwMayBeUncaught(scc, tc) - | - // Incompatible exception type: clause itself - result = scc and - exists(MatchingCompletion mc | - mc.isNonMatch() and - mc.isValidFor(scc) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = mc and - nc.getOuterCompletion() = tc.getOuterCompletion() - ) - ) - or - // Incompatible filter - exists(FalseCompletion fc | - result = lastSpecificCatchClauseFilterClause(scc, fc) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = fc and - nc.getOuterCompletion() = tc.getOuterCompletion() - ) - ) - ) - or - cfe = - any(TryStmt ts | - result = getBlockOrCatchFinallyPred(ts, c) and - ( - // If there is no `finally` block, last elements are from the body, from - // the blocks of one of the `catch` clauses, or from the last `catch` clause - not ts.hasFinally() - or - // Exit completions ignore the `finally` block - c instanceof ExitCompletion - ) - or - result = lastTryStmtFinally(ts, c, any(NormalCompletion nc)) - or - // If the `finally` block completes normally, it inherits any non-normal - // completion that was current before the `finally` block was entered - exists(NormalCompletion finally, Completion outer | - result = lastTryStmtFinally(ts, finally, outer) - | - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = finally and nc.getOuterCompletion() = outer - ) - or - not finally instanceof ConditionalCompletion and - c = outer - ) - ) - } - - /** - * Gets a potential last element executed within control flow element `cfe`, - * as well as its completion, where the last element of `cfe` is recursively - * computed as specified by `rec`. - */ - pragma[nomagic] - private ControlFlowElement lastRec( - TLastRecComputation rec, ControlFlowElement cfe, Completion c - ) { - result = last(lastNonRec(cfe, TRec(rec)), c) - } - - pragma[nomagic] - private ControlFlowElement lastRecSpecific( - ControlFlowElement cfe, Completion c1, Completion c2 - ) { - result = lastRec(TLastRecSpecificCompletion(c2), cfe, c1) - } - - pragma[nomagic] - private ControlFlowElement lastTryStmtBlock(TryStmt ts, Completion c) { - result = last(ts.getBlock(), c) - } - - pragma[nomagic] - private ControlFlowElement lastLastCatchClause(CatchClause cc, Completion c) { - cc.isLast() and - result = last(cc, c) - } - - pragma[nomagic] - private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { - result = last(cc.getBlock(), c) - } - - private ControlFlowElement lastSpecificCatchClauseFilterClause( - SpecificCatchClause scc, Completion c - ) { - result = last(scc.getFilterClause(), c) - } - - /** - * Gets a last element from a `try` or `catch` block of this `try` statement - * that may finish with completion `c`, such that control may be transferred - * to the `finally` block (if it exists). - */ - pragma[nomagic] - private ControlFlowElement getBlockOrCatchFinallyPred(TryStmt ts, Completion c) { - result = lastTryStmtBlock(ts, c) and - ( - // Any non-throw completion from the `try` block will always continue directly - // to the `finally` block - not c instanceof ThrowCompletion - or - // Any completion from the `try` block will continue to the `finally` block - // when there are no catch clauses - not exists(ts.getACatchClause()) - ) - or - // Last element from any of the `catch` clause blocks continues to the `finally` block - result = lastCatchClauseBlock(ts.getACatchClause(), c) - or - // Last element of last `catch` clause continues to the `finally` block - result = lastLastCatchClause(ts.getACatchClause(), c) - } - - pragma[nomagic] - private ControlFlowElement lastTryStmtFinally0(TryStmt ts, Completion c) { - result = last(ts.getFinally(), c) - } - - pragma[nomagic] - ControlFlowElement lastTryStmtFinally(TryStmt ts, NormalCompletion finally, Completion outer) { - result = lastTryStmtFinally0(ts, finally) and - exists(getBlockOrCatchFinallyPred(ts, any(Completion c0 | outer = c0.getOuterCompletion()))) - } - - /** - * Holds if the `try` block that catch clause `last` belongs to may throw an - * exception of type `c`, where no `catch` clause is guaranteed to catch it. - * The catch clause `last` is the last catch clause in the `try` statement - * that it belongs to. - */ - pragma[nomagic] - private predicate throwMayBeUncaught(SpecificCatchClause last, ThrowCompletion c) { - exists(TryStmt ts | - ts = last.getTryStmt() and - exists(lastTryStmtBlock(ts, c)) and - not ts.getACatchClause() instanceof GeneralCatchClause and - forall(SpecificCatchClause scc | scc = ts.getACatchClause() | - scc.hasFilterClause() - or - not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() - ) and - last.isLast() - ) - } - - /** - * Gets a control flow successor for control flow element `cfe`, given that - * `cfe` finishes with completion `c`. - */ - pragma[nomagic] - ControlFlowElement succ(ControlFlowElement cfe, Completion c) { - // Pre-order: flow from element itself to first element of first child - cfe = - any(StandardStmt ss | - result = first(ss.getFirstChildElement()) and - c instanceof SimpleCompletion - ) - or - // Post-order: flow from last element of last child to element itself - cfe = last(result.(StandardExpr).getLastChildElement(), c) and - c instanceof NormalCompletion - or - // Standard left-to-right evaluation - exists(StandardElement parent, int i | - cfe = last(parent.(StandardElement).getNonLastChildElement(i), c) and - c instanceof NormalCompletion and - result = first(parent.getChildElement(i + 1)) - ) - or - // Post-order: flow from last element of operand to element itself - result = - any(LogicalNotExpr lne | - cfe = last(lne.getOperand(), c.(BooleanCompletion).getDual()) - or - cfe = last(lne.getOperand(), c) and - c instanceof SimpleCompletion - ) - or - exists(LogicalAndExpr lae | - // Flow from last element of left operand to first element of right operand - cfe = last(lae.getLeftOperand(), c) and - c instanceof TrueCompletion and - result = first(lae.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(lae.getLeftOperand(), c) and - c instanceof FalseCompletion and - result = lae - or - // Post-order: flow from last element of right operand to element itself - cfe = last(lae.getRightOperand(), c) and - c instanceof NormalCompletion and - result = lae - ) - or - exists(LogicalOrExpr loe | - // Flow from last element of left operand to first element of right operand - cfe = last(loe.getLeftOperand(), c) and - c instanceof FalseCompletion and - result = first(loe.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(loe.getLeftOperand(), c) and - c instanceof TrueCompletion and - result = loe - or - // Post-order: flow from last element of right operand to element itself - cfe = last(loe.getRightOperand(), c) and - c instanceof NormalCompletion and - result = loe - ) - or - exists(NullCoalescingExpr nce | - // Flow from last element of left operand to first element of right operand - cfe = last(nce.getLeftOperand(), c) and - c.(NullnessCompletion).isNull() and - result = first(nce.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(nce.getLeftOperand(), c) and - result = nce and - c instanceof NormalCompletion and - not c.(NullnessCompletion).isNull() - or - // Post-order: flow from last element of right operand to element itself - cfe = last(nce.getRightOperand(), c) and - c instanceof NormalCompletion and - result = nce - ) - or - exists(ConditionalExpr ce | - // Flow from last element of condition to first element of then branch - cfe = last(ce.getCondition(), c) and - c instanceof TrueCompletion and - result = first(ce.getThen()) - or - // Flow from last element of condition to first element of else branch - cfe = last(ce.getCondition(), c) and - c instanceof FalseCompletion and - result = first(ce.getElse()) - or - // Post-order: flow from last element of a branch to element itself - cfe = last([ce.getThen(), ce.getElse()], c) and - c instanceof NormalCompletion and - result = ce - ) - or - exists(ConditionallyQualifiedExpr parent, int i | - cfe = last(getExprChildElement(parent, i), c) and - c instanceof NormalCompletion and - if i = 0 then c.(NullnessCompletion).isNonNull() else any() - | - // Post-order: flow from last element of last child to element itself - i = max(int j | exists(getExprChildElement(parent, j))) and - result = parent - or - // Standard left-to-right evaluation - result = first(getExprChildElement(parent, i + 1)) - ) - or - // Post-order: flow from last element of thrown expression to expression itself - cfe = last(result.(ThrowExpr).getExpr(), c) and - c instanceof NormalCompletion - or - exists(ObjectCreation oc | - // Flow from last element of argument `i` to first element of argument `i+1` - exists(int i | cfe = last(getObjectCreationArgument(oc, i), c) | - result = first(getObjectCreationArgument(oc, i + 1)) and - c instanceof NormalCompletion - ) - or - // Flow from last element of last argument to self - exists(int last | last = max(int i | exists(getObjectCreationArgument(oc, i))) | - cfe = last(getObjectCreationArgument(oc, last), c) and - result = oc and - c instanceof NormalCompletion - ) - or - // Flow from self to first element of initializer - cfe = oc and - result = first(oc.getInitializer()) and - c instanceof SimpleCompletion - ) - or - exists(ArrayCreation ac | - // Flow from self to first element of initializer - cfe = ac and - result = first(ac.getInitializer()) and - c instanceof SimpleCompletion - or - exists(int i | - cfe = last(ac.getLengthArgument(i), c) and - c instanceof SimpleCompletion - | - // Flow from last length argument to self - i = max(int j | exists(ac.getLengthArgument(j))) and - result = ac - or - // Flow from one length argument to the next - result = first(ac.getLengthArgument(i + 1)) - ) - ) - or - exists(IfStmt is | - // Pre-order: flow from statement itself to first element of condition - cfe = is and - result = first(is.getCondition()) and - c instanceof SimpleCompletion - or - cfe = last(is.getCondition(), c) and - ( - // Flow from last element of condition to first element of then branch - c instanceof TrueCompletion and result = first(is.getThen()) - or - // Flow from last element of condition to first element of else branch - c instanceof FalseCompletion and result = first(is.getElse()) - ) - ) - or - exists(Switch s | - // Flow from last element of switch expression to first element of first case - cfe = last(s.getExpr(), c) and - c instanceof NormalCompletion and - result = first(s.getCase(0)) - or - // Flow from last element of case pattern to next case - exists(Case case, int i | case = s.getCase(i) | - cfe = last(case.getPattern(), c) and - c.(MatchingCompletion).isNonMatch() and - result = first(s.getCase(i + 1)) - ) - or - // Flow from last element of condition to next case - exists(Case case, int i | case = s.getCase(i) | - cfe = last(case.getCondition(), c) and - c instanceof FalseCompletion and - result = first(s.getCase(i + 1)) - ) - ) - or - exists(SwitchStmt ss | - // Pre-order: flow from statement itself to first switch expression - cfe = ss and - result = first(ss.getExpr()) and - c instanceof SimpleCompletion - or - // Flow from last element of non-`case` statement `i` to first element of statement `i+1` - exists(int i | cfe = last(ss.getStmt(i), c) | - not ss.getStmt(i) instanceof CaseStmt and - c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) - ) - or - // Flow from last element of `case` statement `i` to first element of statement `i+1` - exists(int i | cfe = last(ss.getStmt(i).(CaseStmt).getBody(), c) | - c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) - ) - ) - or - // Post-order: flow from last element of a case to element itself - cfe = last(result.(SwitchExpr).getACase(), c) and - c instanceof NormalCompletion - or - exists(Case case | - cfe = last(case.getPattern(), c) and - c.(MatchingCompletion).isMatch() and - ( - if exists(case.getCondition()) - then - // Flow from the last element of pattern to the condition - result = first(case.getCondition()) - else - // Flow from last element of pattern to first element of body - result = first(case.getBody()) - ) - or - // Flow from last element of condition to first element of body - cfe = last(case.getCondition(), c) and - c instanceof TrueCompletion and - result = first(case.getBody()) - ) - or - // Pre-order: flow from case itself to first element of pattern - result = first(cfe.(CaseStmt).getPattern()) and - c instanceof SimpleCompletion - or - // Post-order: flow from last element of a case body to element itself - cfe = last(result.(SwitchCaseExpr).getBody(), c) and - c instanceof NormalCompletion - or - // Pre-order: flow from statement itself to first element of statement - cfe = - any(DefaultCase dc | - result = first(dc.getStmt()) and - c instanceof SimpleCompletion - ) - or - exists(LoopStmt ls | - // Flow from last element of condition to first element of loop body - cfe = last(ls.getCondition(), c) and - c instanceof TrueCompletion and - result = first(ls.getBody()) - or - // Flow from last element of loop body back to first element of condition - not ls instanceof ForStmt and - cfe = last(ls.getBody(), c) and - c.continuesLoop() and - result = first(ls.getCondition()) - ) - or - cfe = - any(WhileStmt ws | - // Pre-order: flow from statement itself to first element of condition - result = first(ws.getCondition()) and - c instanceof SimpleCompletion - ) - or - cfe = - any(DoStmt ds | - // Pre-order: flow from statement itself to first element of body - result = first(ds.getBody()) and - c instanceof SimpleCompletion - ) - or - exists(ForStmt fs | - // Pre-order: flow from statement itself to first element of first initializer/ - // condition/loop body - exists(ControlFlowElement next | - cfe = fs and - result = first(next) and - c instanceof SimpleCompletion - | - next = fs.getInitializer(0) - or - not exists(fs.getInitializer(0)) and - next = getForStmtConditionOrBody(fs) - ) - or - // Flow from last element of initializer `i` to first element of initializer `i+1` - exists(int i | cfe = last(fs.getInitializer(i), c) | - c instanceof NormalCompletion and - result = first(fs.getInitializer(i + 1)) - ) - or - // Flow from last element of last initializer to first element of condition/loop body - exists(int last | last = max(int i | exists(fs.getInitializer(i))) | - cfe = last(fs.getInitializer(last), c) and - c instanceof NormalCompletion and - result = first(getForStmtConditionOrBody(fs)) - ) - or - // Flow from last element of condition into first element of loop body - cfe = last(fs.getCondition(), c) and - c instanceof TrueCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body to first element of update/condition/self - exists(ControlFlowElement next | - cfe = last(fs.getBody(), c) and - c.continuesLoop() and - result = first(next) and - if exists(fs.getUpdate(0)) - then next = fs.getUpdate(0) - else next = getForStmtConditionOrBody(fs) - ) - or - // Flow from last element of update to first element of next update/condition/loop body - exists(ControlFlowElement next, int i | - cfe = last(fs.getUpdate(i), c) and - c instanceof NormalCompletion and - result = first(next) and - if exists(fs.getUpdate(i + 1)) - then next = fs.getUpdate(i + 1) - else next = getForStmtConditionOrBody(fs) - ) - ) - or - exists(ForeachStmt fs | - // Flow from last element of iterator expression to emptiness test - cfe = last(fs.getIterableExpr(), c) and - c instanceof NormalCompletion and - result = fs - or - // Flow from emptiness test to first element of variable declaration/loop body - cfe = fs and - c = any(EmptinessCompletion ec | not ec.isEmpty()) and - ( - result = first(fs.getVariableDeclExpr()) - or - result = first(fs.getVariableDeclTuple()) - or - not exists(fs.getVariableDeclExpr()) and - not exists(fs.getVariableDeclTuple()) and - result = first(fs.getBody()) - ) - or - // Flow from last element of variable declaration to first element of loop body - ( - cfe = last(fs.getVariableDeclExpr(), c) or - cfe = last(fs.getVariableDeclTuple(), c) - ) and - c instanceof SimpleCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body back to emptiness test - cfe = last(fs.getBody(), c) and - c.continuesLoop() and - result = fs - ) - or - exists(TryStmt ts | - // Pre-order: flow from statement itself to first element of body - cfe = ts and - result = first(ts.getBlock()) and - c instanceof SimpleCompletion - or - // Flow from last element of body to first `catch` clause - exists(getAThrownException(ts, cfe, c)) and - result = first(ts.getCatchClause(0)) - or - exists(CatchClause cc, int i | cc = ts.getCatchClause(i) | - cfe = cc and - cc = last(ts.getCatchClause(i), c) and - ( - // Flow from one `catch` clause to the next - result = first(ts.getCatchClause(i + 1)) and - c = any(MatchingCompletion mc | not mc.isMatch()) - or - // Flow from last `catch` clause to first element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block - ) - or - cfe = last(ts.getCatchClause(i), c) and - cfe = last(cc.getFilterClause(), _) and - ( - // Flow from last element of `catch` clause filter to next `catch` clause - result = first(ts.getCatchClause(i + 1)) and - c instanceof FalseCompletion - or - // Flow from last element of `catch` clause filter, of last clause, to first - // element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block - ) - or - // Flow from last element of a `catch` block to first element of `finally` block - cfe = lastCatchClauseBlock(cc, c) and - result = first(ts.getFinally()) - ) - or - // Flow from last element of `try` block to first element of `finally` block - cfe = lastTryStmtBlock(ts, c) and - result = first(ts.getFinally()) and - not c instanceof ExitCompletion and - (c instanceof ThrowCompletion implies not exists(ts.getACatchClause())) - ) - or - exists(SpecificCatchClause scc | - // Flow from catch clause to variable declaration/filter clause/block - cfe = scc and - c.(MatchingCompletion).isMatch() and - exists(ControlFlowElement next | result = first(next) | - if exists(scc.getVariableDeclExpr()) - then next = scc.getVariableDeclExpr() - else - if exists(scc.getFilterClause()) - then next = scc.getFilterClause() - else next = scc.getBlock() - ) - or - // Flow from variable declaration to filter clause/block - cfe = last(scc.getVariableDeclExpr(), c) and - c instanceof SimpleCompletion and - exists(ControlFlowElement next | result = first(next) | - if exists(scc.getFilterClause()) - then next = scc.getFilterClause() - else next = scc.getBlock() - ) - or - // Flow from filter to block - cfe = last(scc.getFilterClause(), c) and - c instanceof TrueCompletion and - result = first(scc.getBlock()) - ) - or - // Post-order: flow from last element of child to statement itself - cfe = last(result.(JumpStmt).getChild(0), c) and - c instanceof NormalCompletion - or - exists(ConstructorInitializer ci, Constructor con | - cfe = last(ci, c) and - con = ci.getConstructor() and - c instanceof NormalCompletion - | - // Flow from constructor initializer to first member initializer - exists(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(con, m, 0) - | - result = first(m.getInitializer()) - ) - or - // Flow from constructor initializer to first element of constructor body - not InitializerSplitting::constructorInitializeOrder(con, _, _) and - result = first(con.getBody()) - ) - or - exists(Constructor con, InitializerSplitting::InitializedInstanceMember m, int i | - cfe = last(m.getInitializer(), c) and - c instanceof NormalCompletion and - InitializerSplitting::constructorInitializeOrder(con, m, i) - | - // Flow from one member initializer to the next - exists(InitializerSplitting::InitializedInstanceMember next | - InitializerSplitting::constructorInitializeOrder(con, next, i + 1) and - result = first(next.getInitializer()) - ) - or - // Flow from last member initializer to constructor body - m = InitializerSplitting::lastConstructorInitializer(con) and - result = first(con.getBody()) - ) - or - // Flow from element with `goto` completion to first element of relevant - // target - c = - any(GotoCompletion gc | - cfe = last(_, gc) and - // Special case: when a `goto` happens inside a `try` statement with a - // `finally` block, flow does not go directly to the target, but instead - // to the `finally` block (and from there possibly to the target) - not cfe = getBlockOrCatchFinallyPred(any(TryStmt ts | ts.hasFinally()), _) and - result = first(getLabledStmt(gc.getLabel(), cfe.getEnclosingCallable())) - ) - or - // Standard left-to-right evaluation - exists(QualifiedWriteAccess qwa, int i | - cfe = last(getExprChildElement(qwa, i), c) and - c instanceof NormalCompletion and - result = first(getExprChildElement(qwa, i + 1)) - ) - or - exists(AccessorWrite aw | - // Standard left-to-right evaluation - exists(int i | - cfe = last(getExprChildElement(aw, i), c) and - c instanceof NormalCompletion and - result = first(getExprChildElement(aw, i + 1)) - ) - or - // Flow from last element of last child to first accessor call - cfe = last(getExprChildElement(aw, getLastChildElement(aw)), c) and - result = aw.getCall(0) and - c instanceof NormalCompletion - or - // Flow from one call to the next - exists(int i | cfe = aw.getCall(i) | - result = aw.getCall(i + 1) and - c.isValidFor(cfe) and - c instanceof NormalCompletion - ) - or - // Post-order: flow from last call to element itself - exists(int last | last = max(int i | exists(aw.getCall(i))) | - cfe = aw.getCall(last) and - result = aw and - c.isValidFor(cfe) and - c instanceof NormalCompletion - ) - ) - } - - /** - * Gets an exception type that is thrown by `cfe` in the block of `try` statement - * `ts`. Throw completion `c` matches the exception type. - */ - ExceptionClass getAThrownException(TryStmt ts, ControlFlowElement cfe, ThrowCompletion c) { - cfe = lastTryStmtBlock(ts, c) and - result = c.getExceptionClass() - } - - /** - * Gets the condition of `for` loop `fs` if it exists, otherwise the body. - */ - private ControlFlowElement getForStmtConditionOrBody(ForStmt fs) { - result = fs.getCondition() - or - not exists(fs.getCondition()) and - result = fs.getBody() - } - - /** - * Gets the control flow element that is first executed when entering - * callable `c`. - */ - ControlFlowElement succEntry(@top_level_exprorstmt_parent p) { - p = - any(Callable c | - if exists(c.(Constructor).getInitializer()) - then result = first(c.(Constructor).getInitializer()) - else - if InitializerSplitting::constructorInitializes(c, _) - then - result = - first(any(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(c, m, 0) - ).getInitializer()) - else result = first(c.getBody()) - ) - or - expr_parent_top_level_adjusted(any(Expr e | result = first(e)), _, p) and - not p instanceof Callable and - not p instanceof InitializerSplitting::InitializedInstanceMember - } - - /** - * Gets the callable that is exited when `cfe` finishes with completion `c`, - * if any. - */ - Callable succExit(ControlFlowElement cfe, Completion c) { - cfe = last(result.getBody(), c) and - not c instanceof GotoCompletion - or - exists(InitializerSplitting::InitializedInstanceMember m | - m = InitializerSplitting::lastConstructorInitializer(result) and - cfe = last(m.getInitializer(), c) and - not result.hasBody() - ) - } - } - - import Successor - - cached - private module Cached { - private import semmle.code.csharp.Caching - - private predicate isAbnormalExitType(SuccessorType t) { - t instanceof ExceptionSuccessor or t instanceof ExitSuccessor - } - - /** - * Internal representation of control flow nodes in the control flow graph. - * The control flow graph is pruned for unreachable nodes. - */ - cached - newtype TNode = - TEntryNode(Callable c) { - Stages::ControlFlowStage::forceCachingInSameStage() and - succEntrySplits(c, _, _, _) - } or - TAnnotatedExitNode(Callable c, boolean normal) { - exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) | - succExitSplits(b.getAnElement(), _, c, t) and - if isAbnormalExitType(t) then normal = false else normal = true - ) - } or - TExitNode(Callable c) { - exists(Reachability::SameSplitsBlock b | b.isReachable(_) | - succExitSplits(b.getAnElement(), _, c, _) - ) - } or - TElementNode(ControlFlowElement cfe, Splits splits) { - exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | cfe = b.getAnElement()) - } - - /** Gets a successor node of a given flow type, if any. */ - cached - Node getASuccessorByType(Node pred, SuccessorType t) { - // Callable entry node -> callable body - exists(ControlFlowElement succElement, Splits succSplits | - result = TElementNode(succElement, succSplits) - | - succEntrySplits(pred.(Nodes::EntryNode).getCallable(), succElement, succSplits, t) - ) - or - exists(ControlFlowElement predElement, Splits predSplits | - pred = TElementNode(predElement, predSplits) - | - // Element node -> callable exit (annotated) - result = - any(Nodes::AnnotatedExitNode exit | - succExitSplits(predElement, predSplits, exit.getCallable(), t) and - if isAbnormalExitType(t) then not exit.isNormal() else exit.isNormal() - ) - or - // Element node -> element node - exists(ControlFlowElement succElement, Splits succSplits, Completion c | - result = TElementNode(succElement, succSplits) - | - succSplits(predElement, predSplits, succElement, succSplits, c) and - t.matchesCompletion(c) - ) - ) - or - // Callable exit (annotated) -> callable exit - pred.(Nodes::AnnotatedExitNode).getCallable() = result.(Nodes::ExitNode).getCallable() and - t instanceof SuccessorTypes::NormalSuccessor - } - - /** - * Gets a first control flow element executed within `cfe`. - */ - cached - ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { result = first(cfe) } - - /** - * Gets a potential last control flow element executed within `cfe`. - */ - cached - ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { result = last(cfe, _) } - } - - import Cached - - /** A control flow element that is split into multiple control flow nodes. */ - class SplitControlFlowElement extends ControlFlowElement { - SplitControlFlowElement() { strictcount(this.getAControlFlowNode()) > 1 } - } - } - - private import Internal } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll new file mode 100644 index 00000000000..d458d0be667 --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -0,0 +1,1642 @@ +/** + * Provides auxiliary classes and predicates used to construct the basic successor + * relation on control flow elements. + * + * The implementation is centered around the concept of a _completion_, which + * models how the execution of a statement or expression terminates. + * Completions are represented as an algebraic data type `Completion` defined in + * `Completion.qll`. + * + * The CFG is built by structural recursion over the AST. To achieve this the + * CFG edges related to a given AST node, `n`, are divided into three categories: + * + * 1. The in-going edge that points to the first CFG node to execute when + * `n` is going to be executed. + * 2. The out-going edges for control flow leaving `n` that are going to some + * other node in the surrounding context of `n`. + * 3. The edges that have both of their end-points entirely within the AST + * node and its children. + * + * The edges in (1) and (2) are inherently non-local and are therefore + * initially calculated as half-edges, that is, the single node, `k`, of the + * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`, + * respectively. The edges in (3) can then be enumerated directly by the predicate + * `succ` by calling `first` and `last` recursively on the children of `n` and + * connecting the end-points. This yields the entire CFG, since all edges are in + * (3) for _some_ AST node. + * + * The second parameter of `last` is the completion, which is necessary to distinguish + * the out-going edges from `n`. Note that the completion changes as the calculation of + * `last` proceeds outward through the AST; for example, a `BreakCompletion` is + * caught up by its surrounding loop and turned into a `NormalCompletion`, and a + * `NormalCompletion` proceeds outward through the end of a `finally` block and is + * turned into whatever completion was caught by the `finally`. + * + * An important goal of the CFG is to get the order of side-effects correct. + * Most expressions can have side-effects and must therefore be modeled in the + * CFG in AST post-order. For example, a `MethodCall` evaluates its arguments + * before the call. Most statements do not have side-effects, but merely affect + * the control flow and some could therefore be excluded from the CFG. However, + * as a design choice, all statements are included in the CFG and generally + * serve as their own entry-points, thus executing in some version of AST + * pre-order. + */ + +import csharp +private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow +private import Completion +private import SuccessorType +private import SuccessorTypes +private import Splitting +private import semmle.code.csharp.ExprOrStmtParent + +/** + * A control flow element where the children are evaluated following a + * standard left-to-right evaluation. The actual evaluation order is + * determined by the predicate `getChildElement()`. + */ +abstract private class StandardElement extends ControlFlowElement { + /** Gets the first child element of this element. */ + ControlFlowElement getFirstChildElement() { result = this.getChildElement(0) } + + /** Holds if this element has no children. */ + predicate isLeafElement() { not exists(this.getFirstChildElement()) } + + /** Gets the last child element of this element. */ + ControlFlowElement getLastChildElement() { + exists(int last | + last = max(int i | exists(this.getChildElement(i))) and + result = this.getChildElement(last) + ) + } + + /** Gets the `i`th child element, which is not the last element. */ + pragma[noinline] + ControlFlowElement getNonLastChildElement(int i) { + result = this.getChildElement(i) and + not result = this.getLastChildElement() + } + + /** Gets the `i`th child element, in order of evaluation, starting from 0. */ + abstract ControlFlowElement getChildElement(int i); +} + +private class StandardStmt extends StandardElement, Stmt { + StandardStmt() { + // The following statements need special treatment + not this instanceof IfStmt and + not this instanceof SwitchStmt and + not this instanceof CaseStmt and + not this instanceof LoopStmt and + not this instanceof TryStmt and + not this instanceof SpecificCatchClause and + not this instanceof JumpStmt + } + + override ControlFlowElement getChildElement(int i) { + not this instanceof GeneralCatchClause and + not this instanceof FixedStmt and + not this instanceof UsingBlockStmt and + result = this.getChild(i) + or + this = any(GeneralCatchClause gcc | i = 0 and result = gcc.getBlock()) + or + this = + any(FixedStmt fs | + result = fs.getVariableDeclExpr(i) + or + result = fs.getBody() and + i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 + ) + or + this = + any(UsingBlockStmt us | + if exists(us.getExpr()) + then ( + result = us.getExpr() and + i = 0 + or + result = us.getBody() and + i = 1 + ) else ( + result = us.getVariableDeclExpr(i) + or + result = us.getBody() and + i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 + ) + ) + } +} + +/** + * An assignment operation that has an expanded version. We use the expanded + * version in the control flow graph in order to get better data flow / taint + * tracking. + */ +private class AssignOperationWithExpandedAssignment extends AssignOperation { + AssignOperationWithExpandedAssignment() { this.hasExpandedAssignment() } +} + +/** A conditionally qualified expression. */ +private class ConditionallyQualifiedExpr extends QualifiableExpr { + ConditionallyQualifiedExpr() { this.isConditional() } +} + +/** An expression that should not be included in the control flow graph. */ +abstract private class NoNodeExpr extends Expr { } + +private class SimpleNoNodeExpr extends NoNodeExpr { + SimpleNoNodeExpr() { + this instanceof TypeAccess and + not this = any(PatternMatch pm).getPattern() + } +} + +/** A write access that is not also a read access. */ +private class WriteAccess extends AssignableWrite { + WriteAccess() { + // `x++` is both a read and write access + not this instanceof AssignableRead + } +} + +private class WriteAccessNoNodeExpr extends WriteAccess, NoNodeExpr { + WriteAccessNoNodeExpr() { + // For example a write to a static field, `Foo.Bar = 0`. + forall(Expr e | e = this.getAChildExpr() | e instanceof NoNodeExpr) + } +} + +private ControlFlowElement getExprChildElement0(Expr e, int i) { + not e instanceof NameOfExpr and + not e instanceof QualifiableExpr and + not e instanceof Assignment and + not e instanceof AnonymousFunctionExpr and + result = e.getChild(i) + or + e = any(ExtensionMethodCall emc | result = emc.getArgument(i)) + or + e = + any(QualifiableExpr qe | + not qe instanceof ExtensionMethodCall and + result = qe.getChild(i) + ) + or + e = + any(Assignment a | + // The left-hand side of an assignment is evaluated before the right-hand side + i = 0 and result = a.getLValue() + or + i = 1 and result = a.getRValue() + ) +} + +private ControlFlowElement getExprChildElement(Expr e, int i) { + result = + rank[i + 1](ControlFlowElement cfe, int j | + cfe = getExprChildElement0(e, j) and + not cfe instanceof NoNodeExpr + | + cfe order by j + ) +} + +private int getFirstChildElement(Expr e) { result = min(int i | exists(getExprChildElement(e, i))) } + +private int getLastChildElement(Expr e) { result = max(int i | exists(getExprChildElement(e, i))) } + +/** + * A qualified write access. In a qualified write access, the access itself is + * not evaluated, only the qualifier and the indexer arguments (if any). + */ +private class QualifiedWriteAccess extends WriteAccess, QualifiableExpr { + QualifiedWriteAccess() { + this.hasQualifier() + or + // Member initializers like + // ```csharp + // new Dictionary() { [0] = "Zero", [1] = "One", [2] = "Two" } + // ``` + // need special treatment, because the the accesses `[0]`, `[1]`, and `[2]` + // have no qualifier. + this = any(MemberInitializer mi).getLValue() + } +} + +/** A normal or a (potential) dynamic call to an accessor. */ +private class StatOrDynAccessorCall extends Expr { + StatOrDynAccessorCall() { + this instanceof AccessorCall or + this instanceof DynamicAccess + } +} + +/** + * An expression that writes via an accessor call, for example `x.Prop = 0`, + * where `Prop` is a property. + * + * Accessor writes need special attention, because we need to model the fact + * that the accessor is called *after* the assigned value has been evaluated. + * In the example above, this means we want a CFG that looks like + * + * ```csharp + * x -> 0 -> set_Prop -> x.Prop = 0 + * ``` + */ +class AccessorWrite extends Expr { + AssignableDefinition def; + + AccessorWrite() { + def.getExpr() = this and + def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and + not this instanceof AssignOperationWithExpandedAssignment + } + + /** + * Gets the `i`th accessor being called in this write. More than one call + * can happen in tuple assignments. + */ + StatOrDynAccessorCall getCall(int i) { + result = + rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef | + tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall + | + tdef order by tdef.getEvaluationOrder() + ).getTargetAccess() + or + i = 0 and + result = def.getTargetAccess() and + not def instanceof AssignableDefinitions::TupleAssignmentDefinition + } +} + +private class StandardExpr extends StandardElement, Expr { + StandardExpr() { + // The following expressions need special treatment + not this instanceof LogicalNotExpr and + not this instanceof LogicalAndExpr and + not this instanceof LogicalOrExpr and + not this instanceof NullCoalescingExpr and + not this instanceof ConditionalExpr and + not this instanceof AssignOperationWithExpandedAssignment and + not this instanceof ConditionallyQualifiedExpr and + not this instanceof ThrowExpr and + not this instanceof ObjectCreation and + not this instanceof ArrayCreation and + not this instanceof QualifiedWriteAccess and + not this instanceof AccessorWrite and + not this instanceof NoNodeExpr and + not this instanceof SwitchExpr and + not this instanceof SwitchCaseExpr + } + + override ControlFlowElement getChildElement(int i) { result = getExprChildElement(this, i) } +} + +/** + * Gets the first element executed within control flow element `cfe`. + */ +ControlFlowElement first(ControlFlowElement cfe) { + // Pre-order: element itself + cfe instanceof PreOrderElement and + result = cfe + or + // Post-order: first element of first child (or self, if no children) + cfe = + any(PostOrderElement poe | + result = first(poe.getFirstChild()) + or + not exists(poe.getFirstChild()) and + result = poe + ) + or + cfe = any(AssignOperationWithExpandedAssignment a | result = first(a.getExpandedAssignment())) + or + cfe = any(ConditionallyQualifiedExpr cqe | result = first(getExprChildElement(cqe, 0))) + or + cfe = + any(ObjectCreation oc | + result = first(getObjectCreationArgument(oc, 0)) + or + not exists(getObjectCreationArgument(oc, 0)) and + result = oc + ) + or + cfe = + any(ArrayCreation ac | + // First element of first length argument + result = first(ac.getLengthArgument(0)) + or + // No length argument: element itself + not exists(ac.getLengthArgument(0)) and + result = ac + ) + or + cfe = + any(ForeachStmt fs | + // Unlike most other statements, `foreach` statements are not modelled in + // pre-order, because we use the `foreach` node itself to represent the + // emptiness test that determines whether to execute the loop body + result = first(fs.getIterableExpr()) + ) + or + cfe instanceof QualifiedWriteAccess and + result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) + or + cfe instanceof AccessorWrite and + result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) +} + +private class PreOrderElement extends Stmt { + PreOrderElement() { + this instanceof StandardStmt + or + this instanceof IfStmt + or + this instanceof SwitchStmt + or + this instanceof CaseStmt + or + this instanceof TryStmt + or + this instanceof SpecificCatchClause + or + this instanceof LoopStmt and not this instanceof ForeachStmt + } +} + +private Expr getObjectCreationArgument(ObjectCreation oc, int i) { + i >= 0 and + if oc.hasInitializer() + then result = getExprChildElement(oc, i + 1) + else result = getExprChildElement(oc, i) +} + +private class PostOrderElement extends ControlFlowElement { + PostOrderElement() { + this instanceof StandardExpr + or + this instanceof LogicalNotExpr + or + this instanceof LogicalAndExpr + or + this instanceof LogicalOrExpr + or + this instanceof NullCoalescingExpr + or + this instanceof ConditionalExpr + or + this instanceof SwitchExpr + or + this instanceof SwitchCaseExpr + or + this instanceof JumpStmt + or + this instanceof ThrowExpr + } + + ControlFlowElement getFirstChild() { + result = this.(StandardExpr).getFirstChildElement() + or + result = this.(LogicalNotExpr).getOperand() + or + result = this.(LogicalAndExpr).getLeftOperand() + or + result = this.(LogicalOrExpr).getLeftOperand() + or + result = this.(NullCoalescingExpr).getLeftOperand() + or + result = this.(ConditionalExpr).getCondition() + or + result = this.(SwitchExpr).getExpr() + or + result = this.(SwitchCaseExpr).getPattern() + or + result = this.(JumpStmt).getChild(0) + or + result = this.(ThrowExpr).getExpr() + } +} + +/** A specification of how to compute the last element of a control flow element. */ +private newtype TLastComputation = + /** The element is itself the last element. */ + TSelf(Completion c) or + /** The last element must be computed recursively. */ + TRec(TLastRecComputation c) + +/** + * A specification of how to compute the last element of a control flow element + * using recursion. + */ +private newtype TLastRecComputation = + TLastRecSpecificCompletion(Completion c) or + TLastRecSpecificNegCompletion(Completion c) or + TLastRecAnyCompletion() or + TLastRecNormalCompletion() or + TLastRecAbnormalCompletion() or + TLastRecBreakCompletion() or + TLastRecSwitchAbnormalCompletion() or + TLastRecInvalidOperationException() or + TLastRecNonContinueCompletion() or + TLastRecLoopBodyAbnormal() + +private TSelf getValidSelfCompletion(ControlFlowElement cfe) { + result = TSelf(any(Completion c | c.isValidFor(cfe))) +} + +private TRec specificBoolean(boolean value) { + result = TRec(TLastRecSpecificCompletion(any(BooleanCompletion bc | bc.getValue() = value))) +} + +/** + * Gets an element from which the last element of `cfe` can be computed + * (recursively) based on computation specification `c`. The predicate + * itself is non-recursive. + * + * With the exception of `try` statements, all elements have a simple + * recursive last computation. + */ +pragma[nomagic] +private ControlFlowElement lastNonRec(ControlFlowElement cfe, TLastComputation c) { + // Pre-order: last element of last child (or self, if no children) + cfe = + any(StandardStmt ss | + result = ss.getLastChildElement() and + c = TRec(TLastRecAnyCompletion()) + or + ss.isLeafElement() and + result = ss and + c = getValidSelfCompletion(result) + ) + or + // Post-order: element itself + cfe instanceof PostOrderElement and + result = cfe and + c = getValidSelfCompletion(result) + or + // Pre/post order: a child exits abnormally + result = cfe.(StandardElement).getChildElement(_) and + c = TRec(TLastRecAbnormalCompletion()) + or + // Operand exits abnormally + result = cfe.(LogicalNotExpr).getOperand() and + c = TRec(TLastRecAbnormalCompletion()) + or + // An operand exits abnormally + result = cfe.(LogicalAndExpr).getAnOperand() and + c = TRec(TLastRecAbnormalCompletion()) + or + // An operand exits abnormally + result = cfe.(LogicalOrExpr).getAnOperand() and + c = TRec(TLastRecAbnormalCompletion()) + or + // An operand exits abnormally + result = cfe.(NullCoalescingExpr).getAnOperand() and + c = TRec(TLastRecAbnormalCompletion()) + or + // An operand exits abnormally + result = cfe.(ConditionalExpr).getAnOperand() and + c = TRec(TLastRecAbnormalCompletion()) + or + cfe = + any(AssignOperation ao | + result = ao.getExpandedAssignment() and + c = TRec(TLastRecAnyCompletion()) + ) + or + cfe = + any(ConditionallyQualifiedExpr cqe | + // Post-order: element itself + result = cqe and + c = getValidSelfCompletion(result) + or + // Qualifier exits with a `null` completion + result = getExprChildElement(cqe, 0) and + c = TRec(TLastRecSpecificCompletion(any(NullnessCompletion nc | nc.isNull()))) + ) + or + // Expression being thrown exits abnormally + result = cfe.(ThrowExpr).getExpr() and + c = TRec(TLastRecAbnormalCompletion()) + or + cfe = + any(ObjectCreation oc | + // Post-order: element itself (when no initializer) + result = oc and + not oc.hasInitializer() and + c = getValidSelfCompletion(result) + or + // Last element of initializer + result = oc.getInitializer() and + c = TRec(TLastRecAnyCompletion()) + ) + or + cfe = + any(ArrayCreation ac | + // Post-order: element itself (when no initializer) + result = ac and + not ac.hasInitializer() and + c = getValidSelfCompletion(result) + or + // Last element of initializer + result = ac.getInitializer() and + c = TRec(TLastRecAnyCompletion()) + ) + or + cfe = + any(IfStmt is | + // Condition exits with a false completion and there is no `else` branch + result = is.getCondition() and + c = specificBoolean(false) and + not exists(is.getElse()) + or + // Condition exits abnormally + result = is.getCondition() and + c = TRec(TLastRecAbnormalCompletion()) + or + // Then branch exits with any completion + result = is.getThen() and + c = TRec(TLastRecAnyCompletion()) + or + // Else branch exits with any completion + result = is.getElse() and + c = TRec(TLastRecAnyCompletion()) + ) + or + cfe = + any(Switch s | + // Switch expression exits abnormally + result = s.getExpr() and + c = TRec(TLastRecAbnormalCompletion()) + or + // A case exits abnormally + result = s.getACase() and + c = TRec(TLastRecSwitchAbnormalCompletion()) + ) + or + cfe = + any(SwitchStmt ss | + // Switch expression exits normally and there are no cases + result = ss.getExpr() and + not exists(ss.getACase()) and + c = TRec(TLastRecNormalCompletion()) + or + // A statement exits with a `break` completion + result = ss.getStmt(_) and + c = TRec(TLastRecBreakCompletion()) + or + // A statement exits abnormally + result = ss.getStmt(_) and + c = TRec(TLastRecSwitchAbnormalCompletion()) + or + // Last case exits with a non-match + exists(CaseStmt cs, int last | + last = max(int i | exists(ss.getCase(i))) and + cs = ss.getCase(last) + | + result = cs.getPattern() and + c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) + or + result = cs.getCondition() and + c = specificBoolean(false) + ) + ) + or + cfe = + any(SwitchExpr se | + // Last case exists with a non-match + exists(SwitchCaseExpr sce, int i | + sce = se.getCase(i) and + not sce.matchesAll() and + not exists(se.getCase(i + 1)) and + c = TRec(TLastRecInvalidOperationException()) + | + result = sce.getPattern() or + result = sce.getCondition() + ) + ) + or + cfe = + any(Case case | + // Condition, pattern, or body exists abnormally + result in [case.getCondition(), case.getPattern(), case.getBody()] and + c = TRec(TLastRecAbnormalCompletion()) + ) + or + cfe = + any(CaseStmt case | + // Condition exists with a `false` completion + result = case.getCondition() and + c = specificBoolean(false) + or + // Case pattern exits with a non-match + result = case.getPattern() and + c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) + or + // Case body exits with any completion + result = case.getBody() and + c = TRec(TLastRecAnyCompletion()) + ) + or + exists(LoopStmt ls | + cfe = ls and + not ls instanceof ForeachStmt + | + // Condition exits with a false completion + result = ls.getCondition() and + c = specificBoolean(false) + or + // Condition exits abnormally + result = ls.getCondition() and + c = TRec(TLastRecAbnormalCompletion()) + or + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + result = ls.getBody() and + c = TRec(TLastRecBreakCompletion()) + or + // Body exits with a completion that does not continue the loop + result = ls.getBody() and + c = TRec(TLastRecNonContinueCompletion()) + ) + or + cfe = + any(ForeachStmt fs | + // Iterator expression exits abnormally + result = fs.getIterableExpr() and + c = TRec(TLastRecAbnormalCompletion()) + or + // Emptiness test exits with no more elements + result = fs and + c = TSelf(any(EmptinessCompletion ec | ec.isEmpty())) + or + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + result = fs.getBody() and + c = TRec(TLastRecBreakCompletion()) + or + // Body exits abnormally + result = fs.getBody() and + c = TRec(TLastRecLoopBodyAbnormal()) + ) + or + cfe = + any(TryStmt ts | + // If the `finally` block completes abnormally, take the completion of + // the `finally` block itself + result = ts.getFinally() and + c = TRec(TLastRecAbnormalCompletion()) + ) + or + cfe = + any(SpecificCatchClause scc | + // Last element of `catch` block + result = scc.getBlock() and + c = TRec(TLastRecAnyCompletion()) + or + not scc.isLast() and + ( + // Incompatible exception type: clause itself + result = scc and + c = TSelf(any(MatchingCompletion mc | mc.isNonMatch())) + or + // Incompatible filter + result = scc.getFilterClause() and + c = specificBoolean(false) + ) + ) + or + cfe = + any(JumpStmt js | + // Post-order: element itself + result = js and + c = getValidSelfCompletion(result) + or + // Child exits abnormally + result = js.getChild(0) and + c = TRec(TLastRecAbnormalCompletion()) + ) + or + cfe = + any(QualifiedWriteAccess qwa | + // Skip the access in a qualified write access + result = getExprChildElement(qwa, getLastChildElement(qwa)) and + c = TRec(TLastRecAnyCompletion()) + or + // A child exits abnormally + result = getExprChildElement(qwa, _) and + c = TRec(TLastRecAbnormalCompletion()) + ) + or + cfe = + any(AccessorWrite aw | + // Post-order: element itself + result = aw and + c = getValidSelfCompletion(result) + or + // A child exits abnormally + result = getExprChildElement(aw, _) and + c = TRec(TLastRecAbnormalCompletion()) + or + // An accessor call exits abnormally + result = aw.getCall(_) and + c = + TSelf(any(Completion comp | comp.isValidFor(result) and not comp instanceof NormalCompletion)) + ) +} + +pragma[noinline] +private LabeledStmt getLabledStmt(string label, Callable c) { + result.getEnclosingCallable() = c and + label = result.getLabel() +} + +/** + * Gets a potential last element executed within control flow element `cfe`, + * as well as its completion. + * + * For example, if `cfe` is `A || B` then both `A` and `B` are potential + * last elements with Boolean completions. + */ +ControlFlowElement last(ControlFlowElement cfe, Completion c) { + result = lastNonRec(cfe, TSelf(c)) + or + result = lastRecSpecific(cfe, c, c) + or + exists(TLastRecComputation rec, Completion c0 | result = lastRec(rec, cfe, c0) | + rec = TLastRecSpecificNegCompletion(any(Completion c1 | c1 != c0)) and + c = c0 + or + rec = TLastRecAnyCompletion() and c = c0 + or + rec = TLastRecNormalCompletion() and + c0 instanceof NormalCompletion and + c = c0 + or + rec = TLastRecAbnormalCompletion() and + not c0 instanceof NormalCompletion and + c = c0 + or + rec = TLastRecBreakCompletion() and + c0 instanceof BreakCompletion and + c instanceof BreakNormalCompletion + or + rec = TLastRecSwitchAbnormalCompletion() and + not c instanceof BreakCompletion and + not c instanceof NormalCompletion and + not getLabledStmt(c.(GotoCompletion).getLabel(), cfe.getEnclosingCallable()) instanceof CaseStmt and + c = c0 + or + rec = TLastRecInvalidOperationException() and + (c0.(MatchingCompletion).isNonMatch() or c0 instanceof FalseCompletion) and + c = + any(NestedCompletion nc | + nc.getInnerCompletion() = c0 and + nc + .getOuterCompletion() + .(ThrowCompletion) + .getExceptionClass() + .hasQualifiedName("System.InvalidOperationException") + ) + or + rec = TLastRecNonContinueCompletion() and + not c0 instanceof BreakCompletion and + not c0.continuesLoop() and + c = c0 + or + rec = TLastRecLoopBodyAbnormal() and + not c0 instanceof NormalCompletion and + not c0 instanceof ContinueCompletion and + not c0 instanceof BreakCompletion and + c = c0 + ) + or + // Last `catch` clause inherits throw completions from the `try` block, + // when the clause does not match + exists(SpecificCatchClause scc, ThrowCompletion tc | + scc = cfe and + scc.isLast() and + throwMayBeUncaught(scc, tc) + | + // Incompatible exception type: clause itself + result = scc and + exists(MatchingCompletion mc | + mc.isNonMatch() and + mc.isValidFor(scc) and + c = + any(NestedCompletion nc | + nc.getInnerCompletion() = mc and + nc.getOuterCompletion() = tc.getOuterCompletion() + ) + ) + or + // Incompatible filter + exists(FalseCompletion fc | + result = lastSpecificCatchClauseFilterClause(scc, fc) and + c = + any(NestedCompletion nc | + nc.getInnerCompletion() = fc and + nc.getOuterCompletion() = tc.getOuterCompletion() + ) + ) + ) + or + cfe = + any(TryStmt ts | + result = getBlockOrCatchFinallyPred(ts, c) and + ( + // If there is no `finally` block, last elements are from the body, from + // the blocks of one of the `catch` clauses, or from the last `catch` clause + not ts.hasFinally() + or + // Exit completions ignore the `finally` block + c instanceof ExitCompletion + ) + or + result = lastTryStmtFinally(ts, c, any(NormalCompletion nc)) + or + // If the `finally` block completes normally, it inherits any non-normal + // completion that was current before the `finally` block was entered + exists(NormalCompletion finally, Completion outer | + result = lastTryStmtFinally(ts, finally, outer) + | + c = + any(NestedCompletion nc | + nc.getInnerCompletion() = finally and nc.getOuterCompletion() = outer + ) + or + not finally instanceof ConditionalCompletion and + c = outer + ) + ) +} + +/** + * Gets a potential last element executed within control flow element `cfe`, + * as well as its completion, where the last element of `cfe` is recursively + * computed as specified by `rec`. + */ +pragma[nomagic] +private ControlFlowElement lastRec(TLastRecComputation rec, ControlFlowElement cfe, Completion c) { + result = last(lastNonRec(cfe, TRec(rec)), c) +} + +pragma[nomagic] +private ControlFlowElement lastRecSpecific(ControlFlowElement cfe, Completion c1, Completion c2) { + result = lastRec(TLastRecSpecificCompletion(c2), cfe, c1) +} + +pragma[nomagic] +private ControlFlowElement lastTryStmtBlock(TryStmt ts, Completion c) { + result = last(ts.getBlock(), c) +} + +pragma[nomagic] +private ControlFlowElement lastLastCatchClause(CatchClause cc, Completion c) { + cc.isLast() and + result = last(cc, c) +} + +pragma[nomagic] +private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { + result = last(cc.getBlock(), c) +} + +private ControlFlowElement lastSpecificCatchClauseFilterClause(SpecificCatchClause scc, Completion c) { + result = last(scc.getFilterClause(), c) +} + +/** + * Gets a last element from a `try` or `catch` block of this `try` statement + * that may finish with completion `c`, such that control may be transferred + * to the `finally` block (if it exists). + */ +pragma[nomagic] +private ControlFlowElement getBlockOrCatchFinallyPred(TryStmt ts, Completion c) { + result = lastTryStmtBlock(ts, c) and + ( + // Any non-throw completion from the `try` block will always continue directly + // to the `finally` block + not c instanceof ThrowCompletion + or + // Any completion from the `try` block will continue to the `finally` block + // when there are no catch clauses + not exists(ts.getACatchClause()) + ) + or + // Last element from any of the `catch` clause blocks continues to the `finally` block + result = lastCatchClauseBlock(ts.getACatchClause(), c) + or + // Last element of last `catch` clause continues to the `finally` block + result = lastLastCatchClause(ts.getACatchClause(), c) +} + +pragma[nomagic] +private ControlFlowElement lastTryStmtFinally0(TryStmt ts, Completion c) { + result = last(ts.getFinally(), c) +} + +pragma[nomagic] +ControlFlowElement lastTryStmtFinally(TryStmt ts, NormalCompletion finally, Completion outer) { + result = lastTryStmtFinally0(ts, finally) and + exists(getBlockOrCatchFinallyPred(ts, any(Completion c0 | outer = c0.getOuterCompletion()))) +} + +/** + * Holds if the `try` block that catch clause `last` belongs to may throw an + * exception of type `c`, where no `catch` clause is guaranteed to catch it. + * The catch clause `last` is the last catch clause in the `try` statement + * that it belongs to. + */ +pragma[nomagic] +private predicate throwMayBeUncaught(SpecificCatchClause last, ThrowCompletion c) { + exists(TryStmt ts | + ts = last.getTryStmt() and + exists(lastTryStmtBlock(ts, c)) and + not ts.getACatchClause() instanceof GeneralCatchClause and + forall(SpecificCatchClause scc | scc = ts.getACatchClause() | + scc.hasFilterClause() + or + not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() + ) and + last.isLast() + ) +} + +/** + * Gets a control flow successor for control flow element `cfe`, given that + * `cfe` finishes with completion `c`. + */ +pragma[nomagic] +ControlFlowElement succ(ControlFlowElement cfe, Completion c) { + // Pre-order: flow from element itself to first element of first child + cfe = + any(StandardStmt ss | + result = first(ss.getFirstChildElement()) and + c instanceof SimpleCompletion + ) + or + // Post-order: flow from last element of last child to element itself + cfe = last(result.(StandardExpr).getLastChildElement(), c) and + c instanceof NormalCompletion + or + // Standard left-to-right evaluation + exists(StandardElement parent, int i | + cfe = last(parent.(StandardElement).getNonLastChildElement(i), c) and + c instanceof NormalCompletion and + result = first(parent.getChildElement(i + 1)) + ) + or + // Post-order: flow from last element of operand to element itself + result = + any(LogicalNotExpr lne | + cfe = last(lne.getOperand(), c.(BooleanCompletion).getDual()) + or + cfe = last(lne.getOperand(), c) and + c instanceof SimpleCompletion + ) + or + exists(LogicalAndExpr lae | + // Flow from last element of left operand to first element of right operand + cfe = last(lae.getLeftOperand(), c) and + c instanceof TrueCompletion and + result = first(lae.getRightOperand()) + or + // Post-order: flow from last element of left operand to element itself + cfe = last(lae.getLeftOperand(), c) and + c instanceof FalseCompletion and + result = lae + or + // Post-order: flow from last element of right operand to element itself + cfe = last(lae.getRightOperand(), c) and + c instanceof NormalCompletion and + result = lae + ) + or + exists(LogicalOrExpr loe | + // Flow from last element of left operand to first element of right operand + cfe = last(loe.getLeftOperand(), c) and + c instanceof FalseCompletion and + result = first(loe.getRightOperand()) + or + // Post-order: flow from last element of left operand to element itself + cfe = last(loe.getLeftOperand(), c) and + c instanceof TrueCompletion and + result = loe + or + // Post-order: flow from last element of right operand to element itself + cfe = last(loe.getRightOperand(), c) and + c instanceof NormalCompletion and + result = loe + ) + or + exists(NullCoalescingExpr nce | + // Flow from last element of left operand to first element of right operand + cfe = last(nce.getLeftOperand(), c) and + c.(NullnessCompletion).isNull() and + result = first(nce.getRightOperand()) + or + // Post-order: flow from last element of left operand to element itself + cfe = last(nce.getLeftOperand(), c) and + result = nce and + c instanceof NormalCompletion and + not c.(NullnessCompletion).isNull() + or + // Post-order: flow from last element of right operand to element itself + cfe = last(nce.getRightOperand(), c) and + c instanceof NormalCompletion and + result = nce + ) + or + exists(ConditionalExpr ce | + // Flow from last element of condition to first element of then branch + cfe = last(ce.getCondition(), c) and + c instanceof TrueCompletion and + result = first(ce.getThen()) + or + // Flow from last element of condition to first element of else branch + cfe = last(ce.getCondition(), c) and + c instanceof FalseCompletion and + result = first(ce.getElse()) + or + // Post-order: flow from last element of a branch to element itself + cfe = last([ce.getThen(), ce.getElse()], c) and + c instanceof NormalCompletion and + result = ce + ) + or + exists(ConditionallyQualifiedExpr parent, int i | + cfe = last(getExprChildElement(parent, i), c) and + c instanceof NormalCompletion and + if i = 0 then c.(NullnessCompletion).isNonNull() else any() + | + // Post-order: flow from last element of last child to element itself + i = max(int j | exists(getExprChildElement(parent, j))) and + result = parent + or + // Standard left-to-right evaluation + result = first(getExprChildElement(parent, i + 1)) + ) + or + // Post-order: flow from last element of thrown expression to expression itself + cfe = last(result.(ThrowExpr).getExpr(), c) and + c instanceof NormalCompletion + or + exists(ObjectCreation oc | + // Flow from last element of argument `i` to first element of argument `i+1` + exists(int i | cfe = last(getObjectCreationArgument(oc, i), c) | + result = first(getObjectCreationArgument(oc, i + 1)) and + c instanceof NormalCompletion + ) + or + // Flow from last element of last argument to self + exists(int last | last = max(int i | exists(getObjectCreationArgument(oc, i))) | + cfe = last(getObjectCreationArgument(oc, last), c) and + result = oc and + c instanceof NormalCompletion + ) + or + // Flow from self to first element of initializer + cfe = oc and + result = first(oc.getInitializer()) and + c instanceof SimpleCompletion + ) + or + exists(ArrayCreation ac | + // Flow from self to first element of initializer + cfe = ac and + result = first(ac.getInitializer()) and + c instanceof SimpleCompletion + or + exists(int i | + cfe = last(ac.getLengthArgument(i), c) and + c instanceof SimpleCompletion + | + // Flow from last length argument to self + i = max(int j | exists(ac.getLengthArgument(j))) and + result = ac + or + // Flow from one length argument to the next + result = first(ac.getLengthArgument(i + 1)) + ) + ) + or + exists(IfStmt is | + // Pre-order: flow from statement itself to first element of condition + cfe = is and + result = first(is.getCondition()) and + c instanceof SimpleCompletion + or + cfe = last(is.getCondition(), c) and + ( + // Flow from last element of condition to first element of then branch + c instanceof TrueCompletion and result = first(is.getThen()) + or + // Flow from last element of condition to first element of else branch + c instanceof FalseCompletion and result = first(is.getElse()) + ) + ) + or + exists(Switch s | + // Flow from last element of switch expression to first element of first case + cfe = last(s.getExpr(), c) and + c instanceof NormalCompletion and + result = first(s.getCase(0)) + or + // Flow from last element of case pattern to next case + exists(Case case, int i | case = s.getCase(i) | + cfe = last(case.getPattern(), c) and + c.(MatchingCompletion).isNonMatch() and + result = first(s.getCase(i + 1)) + ) + or + // Flow from last element of condition to next case + exists(Case case, int i | case = s.getCase(i) | + cfe = last(case.getCondition(), c) and + c instanceof FalseCompletion and + result = first(s.getCase(i + 1)) + ) + ) + or + exists(SwitchStmt ss | + // Pre-order: flow from statement itself to first switch expression + cfe = ss and + result = first(ss.getExpr()) and + c instanceof SimpleCompletion + or + // Flow from last element of non-`case` statement `i` to first element of statement `i+1` + exists(int i | cfe = last(ss.getStmt(i), c) | + not ss.getStmt(i) instanceof CaseStmt and + c instanceof NormalCompletion and + result = first(ss.getStmt(i + 1)) + ) + or + // Flow from last element of `case` statement `i` to first element of statement `i+1` + exists(int i | cfe = last(ss.getStmt(i).(CaseStmt).getBody(), c) | + c instanceof NormalCompletion and + result = first(ss.getStmt(i + 1)) + ) + ) + or + // Post-order: flow from last element of a case to element itself + cfe = last(result.(SwitchExpr).getACase(), c) and + c instanceof NormalCompletion + or + exists(Case case | + cfe = last(case.getPattern(), c) and + c.(MatchingCompletion).isMatch() and + ( + if exists(case.getCondition()) + then + // Flow from the last element of pattern to the condition + result = first(case.getCondition()) + else + // Flow from last element of pattern to first element of body + result = first(case.getBody()) + ) + or + // Flow from last element of condition to first element of body + cfe = last(case.getCondition(), c) and + c instanceof TrueCompletion and + result = first(case.getBody()) + ) + or + // Pre-order: flow from case itself to first element of pattern + result = first(cfe.(CaseStmt).getPattern()) and + c instanceof SimpleCompletion + or + // Post-order: flow from last element of a case body to element itself + cfe = last(result.(SwitchCaseExpr).getBody(), c) and + c instanceof NormalCompletion + or + // Pre-order: flow from statement itself to first element of statement + cfe = + any(DefaultCase dc | + result = first(dc.getStmt()) and + c instanceof SimpleCompletion + ) + or + exists(LoopStmt ls | + // Flow from last element of condition to first element of loop body + cfe = last(ls.getCondition(), c) and + c instanceof TrueCompletion and + result = first(ls.getBody()) + or + // Flow from last element of loop body back to first element of condition + not ls instanceof ForStmt and + cfe = last(ls.getBody(), c) and + c.continuesLoop() and + result = first(ls.getCondition()) + ) + or + cfe = + any(WhileStmt ws | + // Pre-order: flow from statement itself to first element of condition + result = first(ws.getCondition()) and + c instanceof SimpleCompletion + ) + or + cfe = + any(DoStmt ds | + // Pre-order: flow from statement itself to first element of body + result = first(ds.getBody()) and + c instanceof SimpleCompletion + ) + or + exists(ForStmt fs | + // Pre-order: flow from statement itself to first element of first initializer/ + // condition/loop body + exists(ControlFlowElement next | + cfe = fs and + result = first(next) and + c instanceof SimpleCompletion + | + next = fs.getInitializer(0) + or + not exists(fs.getInitializer(0)) and + next = getForStmtConditionOrBody(fs) + ) + or + // Flow from last element of initializer `i` to first element of initializer `i+1` + exists(int i | cfe = last(fs.getInitializer(i), c) | + c instanceof NormalCompletion and + result = first(fs.getInitializer(i + 1)) + ) + or + // Flow from last element of last initializer to first element of condition/loop body + exists(int last | last = max(int i | exists(fs.getInitializer(i))) | + cfe = last(fs.getInitializer(last), c) and + c instanceof NormalCompletion and + result = first(getForStmtConditionOrBody(fs)) + ) + or + // Flow from last element of condition into first element of loop body + cfe = last(fs.getCondition(), c) and + c instanceof TrueCompletion and + result = first(fs.getBody()) + or + // Flow from last element of loop body to first element of update/condition/self + exists(ControlFlowElement next | + cfe = last(fs.getBody(), c) and + c.continuesLoop() and + result = first(next) and + if exists(fs.getUpdate(0)) + then next = fs.getUpdate(0) + else next = getForStmtConditionOrBody(fs) + ) + or + // Flow from last element of update to first element of next update/condition/loop body + exists(ControlFlowElement next, int i | + cfe = last(fs.getUpdate(i), c) and + c instanceof NormalCompletion and + result = first(next) and + if exists(fs.getUpdate(i + 1)) + then next = fs.getUpdate(i + 1) + else next = getForStmtConditionOrBody(fs) + ) + ) + or + exists(ForeachStmt fs | + // Flow from last element of iterator expression to emptiness test + cfe = last(fs.getIterableExpr(), c) and + c instanceof NormalCompletion and + result = fs + or + // Flow from emptiness test to first element of variable declaration/loop body + cfe = fs and + c = any(EmptinessCompletion ec | not ec.isEmpty()) and + ( + result = first(fs.getVariableDeclExpr()) + or + result = first(fs.getVariableDeclTuple()) + or + not exists(fs.getVariableDeclExpr()) and + not exists(fs.getVariableDeclTuple()) and + result = first(fs.getBody()) + ) + or + // Flow from last element of variable declaration to first element of loop body + ( + cfe = last(fs.getVariableDeclExpr(), c) or + cfe = last(fs.getVariableDeclTuple(), c) + ) and + c instanceof SimpleCompletion and + result = first(fs.getBody()) + or + // Flow from last element of loop body back to emptiness test + cfe = last(fs.getBody(), c) and + c.continuesLoop() and + result = fs + ) + or + exists(TryStmt ts | + // Pre-order: flow from statement itself to first element of body + cfe = ts and + result = first(ts.getBlock()) and + c instanceof SimpleCompletion + or + // Flow from last element of body to first `catch` clause + exists(getAThrownException(ts, cfe, c)) and + result = first(ts.getCatchClause(0)) + or + exists(CatchClause cc, int i | cc = ts.getCatchClause(i) | + cfe = cc and + cc = last(ts.getCatchClause(i), c) and + ( + // Flow from one `catch` clause to the next + result = first(ts.getCatchClause(i + 1)) and + c = any(MatchingCompletion mc | not mc.isMatch()) + or + // Flow from last `catch` clause to first element of `finally` block + ts.getCatchClause(i).isLast() and + result = first(ts.getFinally()) and + c instanceof ThrowCompletion // inherited from `try` block + ) + or + cfe = last(ts.getCatchClause(i), c) and + cfe = last(cc.getFilterClause(), _) and + ( + // Flow from last element of `catch` clause filter to next `catch` clause + result = first(ts.getCatchClause(i + 1)) and + c instanceof FalseCompletion + or + // Flow from last element of `catch` clause filter, of last clause, to first + // element of `finally` block + ts.getCatchClause(i).isLast() and + result = first(ts.getFinally()) and + c instanceof ThrowCompletion // inherited from `try` block + ) + or + // Flow from last element of a `catch` block to first element of `finally` block + cfe = lastCatchClauseBlock(cc, c) and + result = first(ts.getFinally()) + ) + or + // Flow from last element of `try` block to first element of `finally` block + cfe = lastTryStmtBlock(ts, c) and + result = first(ts.getFinally()) and + not c instanceof ExitCompletion and + (c instanceof ThrowCompletion implies not exists(ts.getACatchClause())) + ) + or + exists(SpecificCatchClause scc | + // Flow from catch clause to variable declaration/filter clause/block + cfe = scc and + c.(MatchingCompletion).isMatch() and + exists(ControlFlowElement next | result = first(next) | + if exists(scc.getVariableDeclExpr()) + then next = scc.getVariableDeclExpr() + else + if exists(scc.getFilterClause()) + then next = scc.getFilterClause() + else next = scc.getBlock() + ) + or + // Flow from variable declaration to filter clause/block + cfe = last(scc.getVariableDeclExpr(), c) and + c instanceof SimpleCompletion and + exists(ControlFlowElement next | result = first(next) | + if exists(scc.getFilterClause()) then next = scc.getFilterClause() else next = scc.getBlock() + ) + or + // Flow from filter to block + cfe = last(scc.getFilterClause(), c) and + c instanceof TrueCompletion and + result = first(scc.getBlock()) + ) + or + // Post-order: flow from last element of child to statement itself + cfe = last(result.(JumpStmt).getChild(0), c) and + c instanceof NormalCompletion + or + exists(ConstructorInitializer ci, Constructor con | + cfe = last(ci, c) and + con = ci.getConstructor() and + c instanceof NormalCompletion + | + // Flow from constructor initializer to first member initializer + exists(InitializerSplitting::InitializedInstanceMember m | + InitializerSplitting::constructorInitializeOrder(con, m, 0) + | + result = first(m.getInitializer()) + ) + or + // Flow from constructor initializer to first element of constructor body + not InitializerSplitting::constructorInitializeOrder(con, _, _) and + result = first(con.getBody()) + ) + or + exists(Constructor con, InitializerSplitting::InitializedInstanceMember m, int i | + cfe = last(m.getInitializer(), c) and + c instanceof NormalCompletion and + InitializerSplitting::constructorInitializeOrder(con, m, i) + | + // Flow from one member initializer to the next + exists(InitializerSplitting::InitializedInstanceMember next | + InitializerSplitting::constructorInitializeOrder(con, next, i + 1) and + result = first(next.getInitializer()) + ) + or + // Flow from last member initializer to constructor body + m = InitializerSplitting::lastConstructorInitializer(con) and + result = first(con.getBody()) + ) + or + // Flow from element with `goto` completion to first element of relevant + // target + c = + any(GotoCompletion gc | + cfe = last(_, gc) and + // Special case: when a `goto` happens inside a `try` statement with a + // `finally` block, flow does not go directly to the target, but instead + // to the `finally` block (and from there possibly to the target) + not cfe = getBlockOrCatchFinallyPred(any(TryStmt ts | ts.hasFinally()), _) and + result = first(getLabledStmt(gc.getLabel(), cfe.getEnclosingCallable())) + ) + or + // Standard left-to-right evaluation + exists(QualifiedWriteAccess qwa, int i | + cfe = last(getExprChildElement(qwa, i), c) and + c instanceof NormalCompletion and + result = first(getExprChildElement(qwa, i + 1)) + ) + or + exists(AccessorWrite aw | + // Standard left-to-right evaluation + exists(int i | + cfe = last(getExprChildElement(aw, i), c) and + c instanceof NormalCompletion and + result = first(getExprChildElement(aw, i + 1)) + ) + or + // Flow from last element of last child to first accessor call + cfe = last(getExprChildElement(aw, getLastChildElement(aw)), c) and + result = aw.getCall(0) and + c instanceof NormalCompletion + or + // Flow from one call to the next + exists(int i | cfe = aw.getCall(i) | + result = aw.getCall(i + 1) and + c.isValidFor(cfe) and + c instanceof NormalCompletion + ) + or + // Post-order: flow from last call to element itself + exists(int last | last = max(int i | exists(aw.getCall(i))) | + cfe = aw.getCall(last) and + result = aw and + c.isValidFor(cfe) and + c instanceof NormalCompletion + ) + ) +} + +/** + * Gets an exception type that is thrown by `cfe` in the block of `try` statement + * `ts`. Throw completion `c` matches the exception type. + */ +ExceptionClass getAThrownException(TryStmt ts, ControlFlowElement cfe, ThrowCompletion c) { + cfe = lastTryStmtBlock(ts, c) and + result = c.getExceptionClass() +} + +/** + * Gets the condition of `for` loop `fs` if it exists, otherwise the body. + */ +private ControlFlowElement getForStmtConditionOrBody(ForStmt fs) { + result = fs.getCondition() + or + not exists(fs.getCondition()) and + result = fs.getBody() +} + +/** + * Gets the control flow element that is first executed when entering + * callable `c`. + */ +ControlFlowElement succEntry(@top_level_exprorstmt_parent p) { + p = + any(Callable c | + if exists(c.(Constructor).getInitializer()) + then result = first(c.(Constructor).getInitializer()) + else + if InitializerSplitting::constructorInitializes(c, _) + then + result = + first(any(InitializerSplitting::InitializedInstanceMember m | + InitializerSplitting::constructorInitializeOrder(c, m, 0) + ).getInitializer()) + else result = first(c.getBody()) + ) + or + expr_parent_top_level_adjusted(any(Expr e | result = first(e)), _, p) and + not p instanceof Callable and + not p instanceof InitializerSplitting::InitializedInstanceMember +} + +/** + * Gets the callable that is exited when `cfe` finishes with completion `c`, + * if any. + */ +Callable succExit(ControlFlowElement cfe, Completion c) { + cfe = last(result.getBody(), c) and + not c instanceof GotoCompletion + or + exists(InitializerSplitting::InitializedInstanceMember m | + m = InitializerSplitting::lastConstructorInitializer(result) and + cfe = last(m.getInitializer(), c) and + not result.hasBody() + ) +} + +cached +private module Cached { + private import semmle.code.csharp.Caching + + private predicate isAbnormalExitType(SuccessorType t) { + t instanceof ExceptionSuccessor or t instanceof ExitSuccessor + } + + /** + * Internal representation of control flow nodes in the control flow graph. + * The control flow graph is pruned for unreachable nodes. + */ + cached + newtype TNode = + TEntryNode(Callable c) { + Stages::ControlFlowStage::forceCachingInSameStage() and + succEntrySplits(c, _, _, _) + } or + TAnnotatedExitNode(Callable c, boolean normal) { + exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) | + succExitSplits(b.getAnElement(), _, c, t) and + if isAbnormalExitType(t) then normal = false else normal = true + ) + } or + TExitNode(Callable c) { + exists(Reachability::SameSplitsBlock b | b.isReachable(_) | + succExitSplits(b.getAnElement(), _, c, _) + ) + } or + TElementNode(ControlFlowElement cfe, Splits splits) { + exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | cfe = b.getAnElement()) + } + + /** Gets a successor node of a given flow type, if any. */ + cached + Node getASuccessorByType(Node pred, SuccessorType t) { + // Callable entry node -> callable body + exists(ControlFlowElement succElement, Splits succSplits | + result = TElementNode(succElement, succSplits) + | + succEntrySplits(pred.(Nodes::EntryNode).getCallable(), succElement, succSplits, t) + ) + or + exists(ControlFlowElement predElement, Splits predSplits | + pred = TElementNode(predElement, predSplits) + | + // Element node -> callable exit (annotated) + result = + any(Nodes::AnnotatedExitNode exit | + succExitSplits(predElement, predSplits, exit.getCallable(), t) and + if isAbnormalExitType(t) then not exit.isNormal() else exit.isNormal() + ) + or + // Element node -> element node + exists(ControlFlowElement succElement, Splits succSplits, Completion c | + result = TElementNode(succElement, succSplits) + | + succSplits(predElement, predSplits, succElement, succSplits, c) and + t.matchesCompletion(c) + ) + ) + or + // Callable exit (annotated) -> callable exit + pred.(Nodes::AnnotatedExitNode).getCallable() = result.(Nodes::ExitNode).getCallable() and + t instanceof SuccessorTypes::NormalSuccessor + } + + /** + * Gets a first control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { result = first(cfe) } + + /** + * Gets a potential last control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { result = last(cfe, _) } +} + +import Cached + +/** A control flow element that is split into multiple control flow nodes. */ +class SplitControlFlowElement extends ControlFlowElement { + SplitControlFlowElement() { strictcount(this.getAControlFlowNode()) > 1 } +} diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll index 3d2de6e39fc..a3f80938ad8 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll @@ -11,7 +11,7 @@ private import semmle.code.cil.CallableReturns private import semmle.code.csharp.ExprOrStmtParent private import semmle.code.csharp.commons.Assertions private import semmle.code.csharp.frameworks.System -private import semmle.code.csharp.controlflow.internal.Completion +private import Completion /** A call that definitely does not return (conservative analysis). */ abstract class NonReturningCall extends Call { diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll index f71ea9dba36..c61e731e8ce 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll @@ -10,9 +10,9 @@ */ import csharp -private import semmle.code.csharp.controlflow.internal.Completion +private import Completion +private import ControlFlowGraphImpl private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow -private import Internal::Successor private predicate startsBB(ControlFlowElement cfe) { not cfe = succ(_, _) and diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll index 032ddff6d7e..3f637256d69 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll @@ -11,8 +11,8 @@ import csharp private import AssignableDefinitions -private import semmle.code.csharp.controlflow.internal.PreBasicBlocks -private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow::Internal::Successor +private import PreBasicBlocks +private import ControlFlowGraphImpl private import semmle.code.csharp.controlflow.Guards as Guards /** diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll index eb65692f6c4..5e5e227a43c 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll @@ -5,11 +5,11 @@ */ import csharp -private import semmle.code.csharp.controlflow.internal.Completion -private import semmle.code.csharp.controlflow.internal.PreSsa as PreSsa -private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow::Internal::Successor -private import ControlFlow +private import Completion +private import PreSsa as PreSsa +private import ControlFlowGraphImpl private import SuccessorTypes +private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow /** The maximum number of splits allowed for a given node. */ private int maxSplits() { result = 5 } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll index 8e34ac19a0d..bd86de5d28b 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll @@ -5,7 +5,7 @@ */ import csharp -private import semmle.code.csharp.controlflow.internal.Completion +private import Completion private import semmle.code.csharp.Caching cached diff --git a/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql b/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql index 7b94129a7d3..6ca83c44889 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql @@ -2,7 +2,8 @@ import csharp import semmle.code.csharp.controlflow.internal.Completion import semmle.code.csharp.controlflow.internal.PreBasicBlocks import ControlFlow -import ControlFlow::Internal +import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl +import semmle.code.csharp.controlflow.internal.Splitting predicate bbStartInconsistency(ControlFlowElement cfe) { exists(ControlFlow::BasicBlock bb | bb.getFirstNode() = cfe.getAControlFlowNode()) and diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql index 0bf986a219c..3c3abea6907 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql @@ -1,6 +1,6 @@ import csharp import Common -import ControlFlow::Internal +import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl from SourceControlFlowElement cfe where cfe.fromSource() diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql index 95cefb41e9c..5a467171176 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql @@ -1,5 +1,5 @@ import csharp -import ControlFlow::Internal +import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl private import semmle.code.csharp.controlflow.internal.Completion import Common diff --git a/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql b/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql index 2e18ddd4c05..b6813cbcc8a 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql @@ -1,7 +1,8 @@ import csharp import ControlFlow import Common -import Internal +import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl +import semmle.code.csharp.controlflow.internal.Splitting import Nodes query predicate booleanNode(ElementNode e, BooleanSplit split) { split = e.getASplit() } diff --git a/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql b/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql index 07ba71fd386..b740f89e91b 100644 --- a/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql +++ b/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql @@ -1,6 +1,6 @@ import csharp import semmle.code.csharp.controlflow.internal.PreSsa as PreSsa -import ControlFlow::Internal +import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl class CallableWithSplitting extends Callable { CallableWithSplitting() { this = any(SplitControlFlowElement e).getEnclosingCallable() } From f3abaa406ce75680292415882e30937cb93768bf Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 18 Nov 2020 09:53:12 +0100 Subject: [PATCH 56/97] C#: Refactor CFG implementation --- .../csharp/controlflow/ControlFlowGraph.qll | 17 +- .../internal/ControlFlowGraphImpl.qll | 2879 +++++++++-------- .../controlflow/internal/PreBasicBlocks.qll | 28 +- .../csharp/controlflow/internal/PreSsa.qll | 32 +- .../csharp/controlflow/internal/Splitting.qll | 312 +- .../controlflow/internal/SuccessorType.qll | 2 +- .../controlflow/graph/BasicBlock.expected | 12 +- .../controlflow/graph/Common.qll | 4 +- .../controlflow/graph/Condition.expected | 18 + .../controlflow/graph/Consistency.ql | 8 +- .../controlflow/graph/Dominance.expected | 83 +- .../graph/EnclosingCallable.expected | 24 + .../controlflow/graph/EntryElement.ql | 6 +- .../controlflow/graph/ExitElement.expected | 5 + .../controlflow/graph/ExitElement.ql | 6 +- .../controlflow/graph/NodeGraph.expected | 24 + .../controlflow/graph/Nodes.expected | 12 + .../library-tests/controlflow/graph/Nodes.ql | 8 +- 18 files changed, 1834 insertions(+), 1646 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index 5fe2f2e5b83..9f4eda99838 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -8,7 +8,7 @@ module ControlFlow { import semmle.code.csharp.controlflow.internal.SuccessorType private import SuccessorTypes private import internal.ControlFlowGraphImpl - private import internal.Splitting + private import internal.Splitting as Splitting /** * A control flow node. @@ -314,7 +314,7 @@ module ControlFlow { * different splits for the element. */ class ElementNode extends Node, TElementNode { - private Splits splits; + private Splitting::Splits splits; private ControlFlowElement cfe; ElementNode() { this = TElementNode(cfe, splits) } @@ -322,7 +322,8 @@ module ControlFlow { override Callable getEnclosingCallable() { result = cfe.getEnclosingCallable() or - result = this.getASplit().(InitializerSplitting::InitializerSplitImpl).getConstructor() + result = + this.getASplit().(Splitting::InitializerSplitting::InitializerSplit).getConstructor() } override ControlFlowElement getElement() { result = cfe } @@ -359,15 +360,15 @@ module ControlFlow { Type getType() { result = e.getType() } } - class Split = SplitImpl; + class Split = Splitting::Split; - class FinallySplit = FinallySplitting::FinallySplitImpl; + class FinallySplit = Splitting::FinallySplitting::FinallySplit; - class ExceptionHandlerSplit = ExceptionHandlerSplitting::ExceptionHandlerSplitImpl; + class ExceptionHandlerSplit = Splitting::ExceptionHandlerSplitting::ExceptionHandlerSplit; - class BooleanSplit = BooleanSplitting::BooleanSplitImpl; + class BooleanSplit = Splitting::BooleanSplitting::BooleanSplit; - class LoopSplit = LoopSplitting::LoopSplitImpl; + class LoopSplit = Splitting::LoopSplitting::LoopSplit; } class BasicBlock = BBs::BasicBlock; diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll index d458d0be667..d076e89d6b0 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -50,704 +50,50 @@ private import SuccessorTypes private import Splitting private import semmle.code.csharp.ExprOrStmtParent -/** - * A control flow element where the children are evaluated following a - * standard left-to-right evaluation. The actual evaluation order is - * determined by the predicate `getChildElement()`. - */ -abstract private class StandardElement extends ControlFlowElement { - /** Gets the first child element of this element. */ - ControlFlowElement getFirstChildElement() { result = this.getChildElement(0) } - - /** Holds if this element has no children. */ - predicate isLeafElement() { not exists(this.getFirstChildElement()) } - - /** Gets the last child element of this element. */ - ControlFlowElement getLastChildElement() { - exists(int last | - last = max(int i | exists(this.getChildElement(i))) and - result = this.getChildElement(last) - ) - } - - /** Gets the `i`th child element, which is not the last element. */ - pragma[noinline] - ControlFlowElement getNonLastChildElement(int i) { - result = this.getChildElement(i) and - not result = this.getLastChildElement() - } - - /** Gets the `i`th child element, in order of evaluation, starting from 0. */ - abstract ControlFlowElement getChildElement(int i); -} - -private class StandardStmt extends StandardElement, Stmt { - StandardStmt() { - // The following statements need special treatment - not this instanceof IfStmt and - not this instanceof SwitchStmt and - not this instanceof CaseStmt and - not this instanceof LoopStmt and - not this instanceof TryStmt and - not this instanceof SpecificCatchClause and - not this instanceof JumpStmt - } - - override ControlFlowElement getChildElement(int i) { - not this instanceof GeneralCatchClause and - not this instanceof FixedStmt and - not this instanceof UsingBlockStmt and - result = this.getChild(i) - or - this = any(GeneralCatchClause gcc | i = 0 and result = gcc.getBlock()) - or - this = - any(FixedStmt fs | - result = fs.getVariableDeclExpr(i) - or - result = fs.getBody() and - i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 - ) - or - this = - any(UsingBlockStmt us | - if exists(us.getExpr()) - then ( - result = us.getExpr() and - i = 0 - or - result = us.getBody() and - i = 1 - ) else ( - result = us.getVariableDeclExpr(i) - or - result = us.getBody() and - i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 - ) - ) - } -} - -/** - * An assignment operation that has an expanded version. We use the expanded - * version in the control flow graph in order to get better data flow / taint - * tracking. - */ -private class AssignOperationWithExpandedAssignment extends AssignOperation { - AssignOperationWithExpandedAssignment() { this.hasExpandedAssignment() } -} - -/** A conditionally qualified expression. */ -private class ConditionallyQualifiedExpr extends QualifiableExpr { - ConditionallyQualifiedExpr() { this.isConditional() } -} - -/** An expression that should not be included in the control flow graph. */ -abstract private class NoNodeExpr extends Expr { } - -private class SimpleNoNodeExpr extends NoNodeExpr { - SimpleNoNodeExpr() { - this instanceof TypeAccess and - not this = any(PatternMatch pm).getPattern() - } -} - -/** A write access that is not also a read access. */ -private class WriteAccess extends AssignableWrite { - WriteAccess() { - // `x++` is both a read and write access - not this instanceof AssignableRead - } -} - -private class WriteAccessNoNodeExpr extends WriteAccess, NoNodeExpr { - WriteAccessNoNodeExpr() { - // For example a write to a static field, `Foo.Bar = 0`. - forall(Expr e | e = this.getAChildExpr() | e instanceof NoNodeExpr) - } -} - -private ControlFlowElement getExprChildElement0(Expr e, int i) { - not e instanceof NameOfExpr and - not e instanceof QualifiableExpr and - not e instanceof Assignment and - not e instanceof AnonymousFunctionExpr and - result = e.getChild(i) - or - e = any(ExtensionMethodCall emc | result = emc.getArgument(i)) - or - e = - any(QualifiableExpr qe | - not qe instanceof ExtensionMethodCall and - result = qe.getChild(i) - ) - or - e = - any(Assignment a | - // The left-hand side of an assignment is evaluated before the right-hand side - i = 0 and result = a.getLValue() - or - i = 1 and result = a.getRValue() - ) -} - -private ControlFlowElement getExprChildElement(Expr e, int i) { - result = - rank[i + 1](ControlFlowElement cfe, int j | - cfe = getExprChildElement0(e, j) and - not cfe instanceof NoNodeExpr - | - cfe order by j - ) -} - -private int getFirstChildElement(Expr e) { result = min(int i | exists(getExprChildElement(e, i))) } - -private int getLastChildElement(Expr e) { result = max(int i | exists(getExprChildElement(e, i))) } - -/** - * A qualified write access. In a qualified write access, the access itself is - * not evaluated, only the qualifier and the indexer arguments (if any). - */ -private class QualifiedWriteAccess extends WriteAccess, QualifiableExpr { - QualifiedWriteAccess() { - this.hasQualifier() - or - // Member initializers like - // ```csharp - // new Dictionary() { [0] = "Zero", [1] = "One", [2] = "Two" } - // ``` - // need special treatment, because the the accesses `[0]`, `[1]`, and `[2]` - // have no qualifier. - this = any(MemberInitializer mi).getLValue() - } -} - -/** A normal or a (potential) dynamic call to an accessor. */ -private class StatOrDynAccessorCall extends Expr { - StatOrDynAccessorCall() { - this instanceof AccessorCall or - this instanceof DynamicAccess - } -} - -/** - * An expression that writes via an accessor call, for example `x.Prop = 0`, - * where `Prop` is a property. - * - * Accessor writes need special attention, because we need to model the fact - * that the accessor is called *after* the assigned value has been evaluated. - * In the example above, this means we want a CFG that looks like - * - * ```csharp - * x -> 0 -> set_Prop -> x.Prop = 0 - * ``` - */ -class AccessorWrite extends Expr { - AssignableDefinition def; - - AccessorWrite() { - def.getExpr() = this and - def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and - not this instanceof AssignOperationWithExpandedAssignment - } +abstract private class ControlFlowTree extends ControlFlowElement { + /** + * Holds if `first` is the first element executed within this control + * flow element. + */ + pragma[nomagic] + abstract predicate first(ControlFlowElement first); /** - * Gets the `i`th accessor being called in this write. More than one call - * can happen in tuple assignments. + * Holds if `last` with completion `c` is a potential last element executed + * within this control flow element. */ - StatOrDynAccessorCall getCall(int i) { - result = - rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef | - tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall - | - tdef order by tdef.getEvaluationOrder() - ).getTargetAccess() - or - i = 0 and - result = def.getTargetAccess() and - not def instanceof AssignableDefinitions::TupleAssignmentDefinition - } -} + pragma[nomagic] + abstract predicate last(ControlFlowElement last, Completion c); -private class StandardExpr extends StandardElement, Expr { - StandardExpr() { - // The following expressions need special treatment - not this instanceof LogicalNotExpr and - not this instanceof LogicalAndExpr and - not this instanceof LogicalOrExpr and - not this instanceof NullCoalescingExpr and - not this instanceof ConditionalExpr and - not this instanceof AssignOperationWithExpandedAssignment and - not this instanceof ConditionallyQualifiedExpr and - not this instanceof ThrowExpr and - not this instanceof ObjectCreation and - not this instanceof ArrayCreation and - not this instanceof QualifiedWriteAccess and - not this instanceof AccessorWrite and - not this instanceof NoNodeExpr and - not this instanceof SwitchExpr and - not this instanceof SwitchCaseExpr - } + /** Holds if abnormal execution of `child` should propagate upwards. */ + abstract predicate propagatesAbnormal(ControlFlowElement child); - override ControlFlowElement getChildElement(int i) { result = getExprChildElement(this, i) } + /** + * Holds if `succ` is a control flow successor for `pred`, given that `pred` + * finishes with completion `c`. + */ + pragma[nomagic] + abstract predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c); } /** - * Gets the first element executed within control flow element `cfe`. + * Holds if `first` is the first element executed within control flow + * element `cft`. */ -ControlFlowElement first(ControlFlowElement cfe) { - // Pre-order: element itself - cfe instanceof PreOrderElement and - result = cfe - or - // Post-order: first element of first child (or self, if no children) - cfe = - any(PostOrderElement poe | - result = first(poe.getFirstChild()) - or - not exists(poe.getFirstChild()) and - result = poe - ) - or - cfe = any(AssignOperationWithExpandedAssignment a | result = first(a.getExpandedAssignment())) - or - cfe = any(ConditionallyQualifiedExpr cqe | result = first(getExprChildElement(cqe, 0))) - or - cfe = - any(ObjectCreation oc | - result = first(getObjectCreationArgument(oc, 0)) - or - not exists(getObjectCreationArgument(oc, 0)) and - result = oc - ) - or - cfe = - any(ArrayCreation ac | - // First element of first length argument - result = first(ac.getLengthArgument(0)) - or - // No length argument: element itself - not exists(ac.getLengthArgument(0)) and - result = ac - ) - or - cfe = - any(ForeachStmt fs | - // Unlike most other statements, `foreach` statements are not modelled in - // pre-order, because we use the `foreach` node itself to represent the - // emptiness test that determines whether to execute the loop body - result = first(fs.getIterableExpr()) - ) - or - cfe instanceof QualifiedWriteAccess and - result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) - or - cfe instanceof AccessorWrite and - result = first(getExprChildElement(cfe, getFirstChildElement(cfe))) -} - -private class PreOrderElement extends Stmt { - PreOrderElement() { - this instanceof StandardStmt - or - this instanceof IfStmt - or - this instanceof SwitchStmt - or - this instanceof CaseStmt - or - this instanceof TryStmt - or - this instanceof SpecificCatchClause - or - this instanceof LoopStmt and not this instanceof ForeachStmt - } -} - -private Expr getObjectCreationArgument(ObjectCreation oc, int i) { - i >= 0 and - if oc.hasInitializer() - then result = getExprChildElement(oc, i + 1) - else result = getExprChildElement(oc, i) -} - -private class PostOrderElement extends ControlFlowElement { - PostOrderElement() { - this instanceof StandardExpr - or - this instanceof LogicalNotExpr - or - this instanceof LogicalAndExpr - or - this instanceof LogicalOrExpr - or - this instanceof NullCoalescingExpr - or - this instanceof ConditionalExpr - or - this instanceof SwitchExpr - or - this instanceof SwitchCaseExpr - or - this instanceof JumpStmt - or - this instanceof ThrowExpr - } - - ControlFlowElement getFirstChild() { - result = this.(StandardExpr).getFirstChildElement() - or - result = this.(LogicalNotExpr).getOperand() - or - result = this.(LogicalAndExpr).getLeftOperand() - or - result = this.(LogicalOrExpr).getLeftOperand() - or - result = this.(NullCoalescingExpr).getLeftOperand() - or - result = this.(ConditionalExpr).getCondition() - or - result = this.(SwitchExpr).getExpr() - or - result = this.(SwitchCaseExpr).getPattern() - or - result = this.(JumpStmt).getChild(0) - or - result = this.(ThrowExpr).getExpr() - } -} - -/** A specification of how to compute the last element of a control flow element. */ -private newtype TLastComputation = - /** The element is itself the last element. */ - TSelf(Completion c) or - /** The last element must be computed recursively. */ - TRec(TLastRecComputation c) +predicate first(ControlFlowTree cft, ControlFlowElement first) { cft.first(first) } /** - * A specification of how to compute the last element of a control flow element - * using recursion. + * Holds if `last` with completion `c` is a potential last element executed + * within control flow element `cft`. */ -private newtype TLastRecComputation = - TLastRecSpecificCompletion(Completion c) or - TLastRecSpecificNegCompletion(Completion c) or - TLastRecAnyCompletion() or - TLastRecNormalCompletion() or - TLastRecAbnormalCompletion() or - TLastRecBreakCompletion() or - TLastRecSwitchAbnormalCompletion() or - TLastRecInvalidOperationException() or - TLastRecNonContinueCompletion() or - TLastRecLoopBodyAbnormal() - -private TSelf getValidSelfCompletion(ControlFlowElement cfe) { - result = TSelf(any(Completion c | c.isValidFor(cfe))) -} - -private TRec specificBoolean(boolean value) { - result = TRec(TLastRecSpecificCompletion(any(BooleanCompletion bc | bc.getValue() = value))) -} - -/** - * Gets an element from which the last element of `cfe` can be computed - * (recursively) based on computation specification `c`. The predicate - * itself is non-recursive. - * - * With the exception of `try` statements, all elements have a simple - * recursive last computation. - */ -pragma[nomagic] -private ControlFlowElement lastNonRec(ControlFlowElement cfe, TLastComputation c) { - // Pre-order: last element of last child (or self, if no children) - cfe = - any(StandardStmt ss | - result = ss.getLastChildElement() and - c = TRec(TLastRecAnyCompletion()) - or - ss.isLeafElement() and - result = ss and - c = getValidSelfCompletion(result) - ) +predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) { + cft.last(last, c) or - // Post-order: element itself - cfe instanceof PostOrderElement and - result = cfe and - c = getValidSelfCompletion(result) - or - // Pre/post order: a child exits abnormally - result = cfe.(StandardElement).getChildElement(_) and - c = TRec(TLastRecAbnormalCompletion()) - or - // Operand exits abnormally - result = cfe.(LogicalNotExpr).getOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(LogicalAndExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(LogicalOrExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(NullCoalescingExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - // An operand exits abnormally - result = cfe.(ConditionalExpr).getAnOperand() and - c = TRec(TLastRecAbnormalCompletion()) - or - cfe = - any(AssignOperation ao | - result = ao.getExpandedAssignment() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(ConditionallyQualifiedExpr cqe | - // Post-order: element itself - result = cqe and - c = getValidSelfCompletion(result) - or - // Qualifier exits with a `null` completion - result = getExprChildElement(cqe, 0) and - c = TRec(TLastRecSpecificCompletion(any(NullnessCompletion nc | nc.isNull()))) - ) - or - // Expression being thrown exits abnormally - result = cfe.(ThrowExpr).getExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - cfe = - any(ObjectCreation oc | - // Post-order: element itself (when no initializer) - result = oc and - not oc.hasInitializer() and - c = getValidSelfCompletion(result) - or - // Last element of initializer - result = oc.getInitializer() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(ArrayCreation ac | - // Post-order: element itself (when no initializer) - result = ac and - not ac.hasInitializer() and - c = getValidSelfCompletion(result) - or - // Last element of initializer - result = ac.getInitializer() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(IfStmt is | - // Condition exits with a false completion and there is no `else` branch - result = is.getCondition() and - c = specificBoolean(false) and - not exists(is.getElse()) - or - // Condition exits abnormally - result = is.getCondition() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Then branch exits with any completion - result = is.getThen() and - c = TRec(TLastRecAnyCompletion()) - or - // Else branch exits with any completion - result = is.getElse() and - c = TRec(TLastRecAnyCompletion()) - ) - or - cfe = - any(Switch s | - // Switch expression exits abnormally - result = s.getExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - // A case exits abnormally - result = s.getACase() and - c = TRec(TLastRecSwitchAbnormalCompletion()) - ) - or - cfe = - any(SwitchStmt ss | - // Switch expression exits normally and there are no cases - result = ss.getExpr() and - not exists(ss.getACase()) and - c = TRec(TLastRecNormalCompletion()) - or - // A statement exits with a `break` completion - result = ss.getStmt(_) and - c = TRec(TLastRecBreakCompletion()) - or - // A statement exits abnormally - result = ss.getStmt(_) and - c = TRec(TLastRecSwitchAbnormalCompletion()) - or - // Last case exits with a non-match - exists(CaseStmt cs, int last | - last = max(int i | exists(ss.getCase(i))) and - cs = ss.getCase(last) - | - result = cs.getPattern() and - c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) - or - result = cs.getCondition() and - c = specificBoolean(false) - ) - ) - or - cfe = - any(SwitchExpr se | - // Last case exists with a non-match - exists(SwitchCaseExpr sce, int i | - sce = se.getCase(i) and - not sce.matchesAll() and - not exists(se.getCase(i + 1)) and - c = TRec(TLastRecInvalidOperationException()) - | - result = sce.getPattern() or - result = sce.getCondition() - ) - ) - or - cfe = - any(Case case | - // Condition, pattern, or body exists abnormally - result in [case.getCondition(), case.getPattern(), case.getBody()] and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(CaseStmt case | - // Condition exists with a `false` completion - result = case.getCondition() and - c = specificBoolean(false) - or - // Case pattern exits with a non-match - result = case.getPattern() and - c = TRec(TLastRecSpecificNegCompletion(any(MatchingCompletion mc | mc.isMatch()))) - or - // Case body exits with any completion - result = case.getBody() and - c = TRec(TLastRecAnyCompletion()) - ) - or - exists(LoopStmt ls | - cfe = ls and - not ls instanceof ForeachStmt - | - // Condition exits with a false completion - result = ls.getCondition() and - c = specificBoolean(false) - or - // Condition exits abnormally - result = ls.getCondition() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - result = ls.getBody() and - c = TRec(TLastRecBreakCompletion()) - or - // Body exits with a completion that does not continue the loop - result = ls.getBody() and - c = TRec(TLastRecNonContinueCompletion()) + exists(ControlFlowElement cfe | + cft.propagatesAbnormal(cfe) and + last(cfe, last, c) and + not c instanceof NormalCompletion ) - or - cfe = - any(ForeachStmt fs | - // Iterator expression exits abnormally - result = fs.getIterableExpr() and - c = TRec(TLastRecAbnormalCompletion()) - or - // Emptiness test exits with no more elements - result = fs and - c = TSelf(any(EmptinessCompletion ec | ec.isEmpty())) - or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - result = fs.getBody() and - c = TRec(TLastRecBreakCompletion()) - or - // Body exits abnormally - result = fs.getBody() and - c = TRec(TLastRecLoopBodyAbnormal()) - ) - or - cfe = - any(TryStmt ts | - // If the `finally` block completes abnormally, take the completion of - // the `finally` block itself - result = ts.getFinally() and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(SpecificCatchClause scc | - // Last element of `catch` block - result = scc.getBlock() and - c = TRec(TLastRecAnyCompletion()) - or - not scc.isLast() and - ( - // Incompatible exception type: clause itself - result = scc and - c = TSelf(any(MatchingCompletion mc | mc.isNonMatch())) - or - // Incompatible filter - result = scc.getFilterClause() and - c = specificBoolean(false) - ) - ) - or - cfe = - any(JumpStmt js | - // Post-order: element itself - result = js and - c = getValidSelfCompletion(result) - or - // Child exits abnormally - result = js.getChild(0) and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(QualifiedWriteAccess qwa | - // Skip the access in a qualified write access - result = getExprChildElement(qwa, getLastChildElement(qwa)) and - c = TRec(TLastRecAnyCompletion()) - or - // A child exits abnormally - result = getExprChildElement(qwa, _) and - c = TRec(TLastRecAbnormalCompletion()) - ) - or - cfe = - any(AccessorWrite aw | - // Post-order: element itself - result = aw and - c = getValidSelfCompletion(result) - or - // A child exits abnormally - result = getExprChildElement(aw, _) and - c = TRec(TLastRecAbnormalCompletion()) - or - // An accessor call exits abnormally - result = aw.getCall(_) and - c = - TSelf(any(Completion comp | comp.isValidFor(result) and not comp instanceof NormalCompletion)) - ) } pragma[noinline] @@ -756,115 +102,1280 @@ private LabeledStmt getLabledStmt(string label, Callable c) { label = result.getLabel() } +pragma[nomagic] +private predicate goto(ControlFlowElement cfe, GotoCompletion gc, string label, Callable enclosing) { + last(_, cfe, gc) and + // Special case: when a `goto` happens inside a `try` statement with a + // `finally` block, flow does not go directly to the target, but instead + // to the `finally` block (and from there possibly to the target) + not cfe = any(Statements::TryStmtTree t | t.hasFinally()).getBlockOrCatchFinallyPred(_) and + label = gc.getLabel() and + enclosing = cfe.getEnclosingCallable() +} + /** - * Gets a potential last element executed within control flow element `cfe`, - * as well as its completion. - * - * For example, if `cfe` is `A || B` then both `A` and `B` are potential - * last elements with Boolean completions. + * Holds if `succ` is a control flow successor for `pred`, given that `pred` + * finishes with completion `c`. */ -ControlFlowElement last(ControlFlowElement cfe, Completion c) { - result = lastNonRec(cfe, TSelf(c)) +pragma[nomagic] +predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + any(ControlFlowTree cft).succ(pred, succ, c) or - result = lastRecSpecific(cfe, c, c) + exists(Constructor con, InitializerSplitting::InitializedInstanceMember m, int i | + last(m.getInitializer(), pred, c) and + c instanceof NormalCompletion and + InitializerSplitting::constructorInitializeOrder(con, m, i) + | + // Flow from one member initializer to the next + exists(InitializerSplitting::InitializedInstanceMember next | + InitializerSplitting::constructorInitializeOrder(con, next, i + 1) and + first(next.getInitializer(), succ) + ) + or + // Flow from last member initializer to constructor body + m = InitializerSplitting::lastConstructorInitializer(con) and + first(con.getBody(), succ) + ) or - exists(TLastRecComputation rec, Completion c0 | result = lastRec(rec, cfe, c0) | - rec = TLastRecSpecificNegCompletion(any(Completion c1 | c1 != c0)) and - c = c0 + // Flow from element with `goto` completion to first element of relevant + // target + exists(string label, Callable enclosing | + goto(pred, c, label, enclosing) and + first(getLabledStmt(label, enclosing), succ) + ) +} + +/** Holds if `first` is first executed when entering `scope`. */ +predicate succEntry(@top_level_exprorstmt_parent scope, ControlFlowElement first) { + scope = + any(Callable c | + if exists(c.(Constructor).getInitializer()) + then first(c.(Constructor).getInitializer(), first) + else + if InitializerSplitting::constructorInitializes(c, _) + then + first(any(InitializerSplitting::InitializedInstanceMember m | + InitializerSplitting::constructorInitializeOrder(c, m, 0) + ).getInitializer(), first) + else first(c.getBody(), first) + ) + or + expr_parent_top_level_adjusted(any(Expr e | first(e, first)), _, scope) and + not scope instanceof Callable and + not scope instanceof InitializerSplitting::InitializedInstanceMember +} + +/** Holds if `scope` is exited when `last` finishes with completion `c`. */ +predicate succExit(ControlFlowElement last, Callable scope, Completion c) { + last(scope.getBody(), last, c) and + not c instanceof GotoCompletion + or + exists(InitializerSplitting::InitializedInstanceMember m | + m = InitializerSplitting::lastConstructorInitializer(scope) and + last(m.getInitializer(), last, c) and + not scope.hasBody() + ) +} + +/** + * A control flow element where the children are evaluated following a + * standard left-to-right evaluation. The actual evaluation order is + * determined by the predicate `getChildElement()`. + */ +abstract private class StandardElement extends ControlFlowTree { + /** Gets the `i`th child element, in order of evaluation, starting from 0. */ + abstract ControlFlowElement getChildElement(int i); + + /** Gets the first child element of this element. */ + final ControlFlowElement getFirstChild() { result = this.getChildElement(0) } + + /** Holds if this element has no children. */ + final predicate isLeafElement() { not exists(this.getFirstChild()) } + + /** Gets the last child element of this element. */ + final ControlFlowTree getLastChild() { + exists(int last | + result = this.getChildElement(last) and + not exists(this.getChildElement(last + 1)) + ) + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getChildElement(_) + } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + exists(int i | + last(this.getChildElement(i), pred, c) and + first(this.getChildElement(i + 1), succ) and + c instanceof NormalCompletion + ) + } +} + +abstract private class PreOrderTree extends ControlFlowTree { + final override predicate first(ControlFlowElement first) { first = this } +} + +abstract private class PostOrderTree extends ControlFlowTree { + override predicate last(ControlFlowElement last, Completion c) { + last = this and + c.isValidFor(last) + } +} + +abstract private class SwitchTree extends ControlFlowTree, Switch { + Expr expr; + + SwitchTree() { expr = this.getExpr() } + + override predicate propagatesAbnormal(ControlFlowElement child) { child = expr } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of switch expression to first element of first case + last(expr, pred, c) and + c instanceof NormalCompletion and + first(this.getCase(0), succ) or - rec = TLastRecAnyCompletion() and c = c0 + // Flow from last element of case pattern to next case + exists(Case case, int i | case = this.getCase(i) | + last(case.getPattern(), pred, c) and + c.(MatchingCompletion).isNonMatch() and + first(this.getCase(i + 1), succ) + ) or - rec = TLastRecNormalCompletion() and - c0 instanceof NormalCompletion and - c = c0 + // Flow from last element of condition to next case + exists(Case case, int i | case = this.getCase(i) | + last(case.getCondition(), pred, c) and + c instanceof FalseCompletion and + first(this.getCase(i + 1), succ) + ) + } +} + +abstract private class CaseTree extends ControlFlowTree, Case { + PatternExpr pattern; + ControlFlowElement body; + + CaseTree() { pattern = this.getPattern() and body = this.getBody() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child in [pattern, this.getCondition().(ControlFlowElement), body] + } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + last(pattern, pred, c) and + c.(MatchingCompletion).isMatch() and + ( + if exists(this.getCondition()) + then + // Flow from the last element of pattern to the condition + first(this.getCondition(), succ) + else + // Flow from last element of pattern to first element of body + first(body, succ) + ) or - rec = TLastRecAbnormalCompletion() and - not c0 instanceof NormalCompletion and - c = c0 + // Flow from last element of condition to first element of body + last(this.getCondition(), pred, c) and + c instanceof TrueCompletion and + first(body, succ) + } +} + +module Expressions { + /** An expression that should not be included in the control flow graph. */ + abstract private class NoNodeExpr extends Expr { } + + private class SimpleNoNodeExpr extends NoNodeExpr { + SimpleNoNodeExpr() { + this instanceof TypeAccess and + not this = any(PatternMatch pm).getPattern() + } + } + + /** A write access that is not also a read access. */ + private class WriteAccess extends AssignableWrite { + WriteAccess() { + // `x++` is both a read and write access + not this instanceof AssignableRead + } + } + + private class WriteAccessNoNodeExpr extends WriteAccess, NoNodeExpr { + WriteAccessNoNodeExpr() { + // For example a write to a static field, `Foo.Bar = 0`. + forall(Expr e | e = this.getAChildExpr() | e instanceof NoNodeExpr) + } + } + + private ControlFlowElement getExprChild0(Expr e, int i) { + not e instanceof NameOfExpr and + not e instanceof QualifiableExpr and + not e instanceof Assignment and + not e instanceof AnonymousFunctionExpr and + result = e.getChild(i) or - rec = TLastRecBreakCompletion() and - c0 instanceof BreakCompletion and - c instanceof BreakNormalCompletion + e = any(ExtensionMethodCall emc | result = emc.getArgument(i)) or - rec = TLastRecSwitchAbnormalCompletion() and - not c instanceof BreakCompletion and - not c instanceof NormalCompletion and - not getLabledStmt(c.(GotoCompletion).getLabel(), cfe.getEnclosingCallable()) instanceof CaseStmt and - c = c0 - or - rec = TLastRecInvalidOperationException() and - (c0.(MatchingCompletion).isNonMatch() or c0 instanceof FalseCompletion) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = c0 and - nc - .getOuterCompletion() - .(ThrowCompletion) - .getExceptionClass() - .hasQualifiedName("System.InvalidOperationException") + e = + any(QualifiableExpr qe | + not qe instanceof ExtensionMethodCall and + result = qe.getChild(i) ) or - rec = TLastRecNonContinueCompletion() and - not c0 instanceof BreakCompletion and - not c0.continuesLoop() and - c = c0 - or - rec = TLastRecLoopBodyAbnormal() and - not c0 instanceof NormalCompletion and - not c0 instanceof ContinueCompletion and - not c0 instanceof BreakCompletion and - c = c0 - ) - or - // Last `catch` clause inherits throw completions from the `try` block, - // when the clause does not match - exists(SpecificCatchClause scc, ThrowCompletion tc | - scc = cfe and - scc.isLast() and - throwMayBeUncaught(scc, tc) - | - // Incompatible exception type: clause itself - result = scc and - exists(MatchingCompletion mc | - mc.isNonMatch() and - mc.isValidFor(scc) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = mc and - nc.getOuterCompletion() = tc.getOuterCompletion() - ) + e = + any(Assignment a | + // The left-hand side of an assignment is evaluated before the right-hand side + i = 0 and result = a.getLValue() + or + i = 1 and result = a.getRValue() + ) + } + + private ControlFlowElement getExprChild(Expr e, int i) { + result = + rank[i + 1](ControlFlowElement cfe, int j | + cfe = getExprChild0(e, j) and + not cfe instanceof NoNodeExpr + | + cfe order by j + ) + } + + private ControlFlowElement getLastExprChild(Expr e) { + exists(int last | + result = getExprChild(e, last) and + not exists(getExprChild(e, last + 1)) ) - or - // Incompatible filter - exists(FalseCompletion fc | - result = lastSpecificCatchClauseFilterClause(scc, fc) and - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = fc and - nc.getOuterCompletion() = tc.getOuterCompletion() + } + + private class StandardExpr extends StandardElement, PostOrderTree, Expr { + StandardExpr() { + // The following expressions need special treatment + not this instanceof LogicalNotExpr and + not this instanceof LogicalAndExpr and + not this instanceof LogicalOrExpr and + not this instanceof NullCoalescingExpr and + not this instanceof ConditionalExpr and + not this instanceof AssignOperationWithExpandedAssignment and + not this instanceof ConditionallyQualifiedExpr and + not this instanceof ThrowExpr and + not this instanceof ObjectCreation and + not this instanceof ArrayCreation and + not this instanceof QualifiedWriteAccess and + not this instanceof AccessorWrite and + not this instanceof NoNodeExpr and + not this instanceof SwitchExpr and + not this instanceof SwitchCaseExpr and + not this instanceof ConstructorInitializer + } + + final override ControlFlowTree getChildElement(int i) { result = getExprChild(this, i) } + + final override predicate first(ControlFlowElement first) { + first(this.getFirstChild(), first) + or + not exists(this.getFirstChild()) and + first = this + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + StandardElement.super.succ(pred, succ, c) + or + last(this.getLastChild(), pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + /** + * A qualified write access. In a qualified write access, the access itself is + * not evaluated, only the qualifier and the indexer arguments (if any). + */ + private class QualifiedWriteAccess extends WriteAccess, QualifiableExpr, ControlFlowTree { + QualifiedWriteAccess() { + this.hasQualifier() + or + // Member initializers like + // ```csharp + // new Dictionary() { [0] = "Zero", [1] = "One", [2] = "Two" } + // ``` + // need special treatment, because the the accesses `[0]`, `[1]`, and `[2]` + // have no qualifier. + this = any(MemberInitializer mi).getLValue() + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = getExprChild(this, _) + } + + final override predicate first(ControlFlowElement first) { first(getExprChild(this, 0), first) } + + final override predicate last(ControlFlowElement last, Completion c) { + // Skip the access in a qualified write access + last(getLastExprChild(this), last, c) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + exists(int i | + last(getExprChild(this, i), pred, c) and + c instanceof NormalCompletion and + first(getExprChild(this, i + 1), succ) + ) + } + } + + /** A normal or a (potential) dynamic call to an accessor. */ + private class StatOrDynAccessorCall extends Expr { + StatOrDynAccessorCall() { + this instanceof AccessorCall or + this instanceof DynamicAccess + } + } + + /** + * An expression that writes via an accessor call, for example `x.Prop = 0`, + * where `Prop` is a property. + * + * Accessor writes need special attention, because we need to model the fact + * that the accessor is called *after* the assigned value has been evaluated. + * In the example above, this means we want a CFG that looks like + * + * ```csharp + * x -> 0 -> set_Prop -> x.Prop = 0 + * ``` + */ + class AccessorWrite extends Expr, PostOrderTree { + AssignableDefinition def; + + AccessorWrite() { + def.getExpr() = this and + def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and + not this instanceof AssignOperationWithExpandedAssignment + } + + /** + * Gets the `i`th accessor being called in this write. More than one call + * can happen in tuple assignments. + */ + StatOrDynAccessorCall getCall(int i) { + result = + rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef | + tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall + | + tdef order by tdef.getEvaluationOrder() + ).getTargetAccess() + or + i = 0 and + result = def.getTargetAccess() and + not def instanceof AssignableDefinitions::TupleAssignmentDefinition + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = getExprChild(this, _) + or + child = this.getCall(_) + } + + final override predicate first(ControlFlowElement first) { first(getExprChild(this, 0), first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Standard left-to-right evaluation + exists(int i | + last(getExprChild(this, i), pred, c) and + c instanceof NormalCompletion and + first(getExprChild(this, i + 1), succ) + ) + or + // Flow from last element of last child to first accessor call + last(getLastExprChild(this), pred, c) and + succ = this.getCall(0) and + c instanceof NormalCompletion + or + // Flow from one call to the next + exists(int i | pred = this.getCall(i) | + succ = this.getCall(i + 1) and + c.isValidFor(pred) and + c instanceof NormalCompletion + ) + or + // Post-order: flow from last call to element itself + exists(int last | last = max(int i | exists(this.getCall(i))) | + pred = this.getCall(last) and + succ = this and + c.isValidFor(pred) and + c instanceof NormalCompletion + ) + } + } + + private class LogicalNotExprTree extends PostOrderTree, LogicalNotExpr { + private Expr operand; + + LogicalNotExprTree() { operand = this.getOperand() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getOperand() + } + + final override predicate first(ControlFlowElement first) { first(operand, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + succ = this and + ( + last(operand, pred, c.(BooleanCompletion).getDual()) + or + last(operand, pred, c) and + c instanceof SimpleCompletion + ) + } + } + + private class LogicalAndExprTree extends PostOrderTree, LogicalAndExpr { + private Expr left; + private Expr right; + + LogicalAndExprTree() { left = this.getLeftOperand() and right = this.getRightOperand() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child in [left, right] } + + final override predicate first(ControlFlowElement first) { first(left, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of left operand to first element of right operand + last(left, pred, c) and + c instanceof TrueCompletion and + first(right, succ) + or + // Post-order: flow from last element of left operand to element itself + last(left, pred, c) and + c instanceof FalseCompletion and + succ = this + or + // Post-order: flow from last element of right operand to element itself + last(right, pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class LogicalOrExprTree extends PostOrderTree, LogicalOrExpr { + private Expr left; + private Expr right; + + LogicalOrExprTree() { left = this.getLeftOperand() and right = this.getRightOperand() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child in [left, right] } + + final override predicate first(ControlFlowElement first) { first(left, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of left operand to first element of right operand + last(left, pred, c) and + c instanceof FalseCompletion and + first(right, succ) + or + // Post-order: flow from last element of left operand to element itself + last(left, pred, c) and + c instanceof TrueCompletion and + succ = this + or + // Post-order: flow from last element of right operand to element itself + last(right, pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class NullCoalescingExprTree extends PostOrderTree, NullCoalescingExpr { + private Expr left; + private Expr right; + + NullCoalescingExprTree() { left = this.getLeftOperand() and right = this.getRightOperand() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child in [left, right] } + + final override predicate first(ControlFlowElement first) { first(left, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of left operand to first element of right operand + last(left, pred, c) and + c.(NullnessCompletion).isNull() and + first(right, succ) + or + // Post-order: flow from last element of left operand to element itself + last(left, pred, c) and + succ = this and + c instanceof NormalCompletion and + not c.(NullnessCompletion).isNull() + or + // Post-order: flow from last element of right operand to element itself + last(right, pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class ConditionalExprTree extends PostOrderTree, ConditionalExpr { + private Expr condition; + private Expr thenBranch; + private Expr elseBranch; + + ConditionalExprTree() { + condition = this.getCondition() and + thenBranch = this.getThen() and + elseBranch = this.getElse() + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child in [condition, thenBranch, elseBranch] + } + + final override predicate first(ControlFlowElement first) { first(condition, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of condition to first element of then branch + last(condition, pred, c) and + c instanceof TrueCompletion and + first(thenBranch, succ) + or + // Flow from last element of condition to first element of else branch + last(condition, pred, c) and + c instanceof FalseCompletion and + first(elseBranch, succ) + or + // Post-order: flow from last element of a branch to element itself + last([thenBranch, elseBranch], pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + /** + * An assignment operation that has an expanded version. We use the expanded + * version in the control flow graph in order to get better data flow / taint + * tracking. + */ + private class AssignOperationWithExpandedAssignment extends AssignOperation, ControlFlowTree { + private Expr expanded; + + AssignOperationWithExpandedAssignment() { expanded = this.getExpandedAssignment() } + + final override predicate first(ControlFlowElement first) { first(expanded, first) } + + final override predicate last(ControlFlowElement last, Completion c) { + last = expanded and + last(expanded, last, c) + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { none() } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + none() + } + } + + /** A conditionally qualified expression. */ + private class ConditionallyQualifiedExpr extends PostOrderTree, QualifiableExpr { + private Expr qualifier; + + ConditionallyQualifiedExpr() { this.isConditional() and qualifier = getExprChild(this, 0) } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child = qualifier } + + final override predicate first(ControlFlowElement first) { first(qualifier, first) } + + pragma[nomagic] + private predicate lastQualifier(ControlFlowElement last, Completion c) { + last(qualifier, last, c) + } + + final override predicate last(ControlFlowElement last, Completion c) { + PostOrderTree.super.last(last, c) + or + // Qualifier exits with a `null` completion + lastQualifier(last, c) and + c.(NullnessCompletion).isNull() + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + exists(int i | + last(getExprChild(this, i), pred, c) and + c instanceof NormalCompletion and + if i = 0 then c.(NullnessCompletion).isNonNull() else any() + | + // Post-order: flow from last element of last child to element itself + i = max(int j | exists(getExprChild(this, j))) and + succ = this + or + // Standard left-to-right evaluation + first(getExprChild(this, i + 1), succ) + ) + } + } + + private class ThrowExprTree extends PostOrderTree, ThrowExpr { + private Expr expr; + + ThrowExprTree() { expr = this.getExpr() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child = expr } + + final override predicate first(ControlFlowElement first) { first(expr, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + last(expr, pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class ObjectCreationTree extends ControlFlowTree, ObjectCreation { + private Expr getObjectCreationArgument(int i) { + i >= 0 and + if this.hasInitializer() + then result = getExprChild(this, i + 1) + else result = getExprChild(this, i) + } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getObjectCreationArgument(_) + } + + final override predicate first(ControlFlowElement first) { + first(this.getObjectCreationArgument(0), first) + or + not exists(this.getObjectCreationArgument(0)) and + first = this + } + + final override predicate last(ControlFlowElement last, Completion c) { + // Post-order: element itself (when no initializer) + last = this and + not this.hasInitializer() and + c.isValidFor(this) + or + // Last element of initializer + last(this.getInitializer(), last, c) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of argument `i` to first element of argument `i+1` + exists(int i | last(this.getObjectCreationArgument(i), pred, c) | + first(this.getObjectCreationArgument(i + 1), succ) and + c instanceof NormalCompletion + ) + or + // Flow from last element of last argument to self + exists(int last | last = max(int i | exists(this.getObjectCreationArgument(i))) | + last(this.getObjectCreationArgument(last), pred, c) and + succ = this and + c instanceof NormalCompletion + ) + or + // Flow from self to first element of initializer + pred = this and + first(this.getInitializer(), succ) and + c instanceof SimpleCompletion + } + } + + private class ArrayCreationTree extends ControlFlowTree, ArrayCreation { + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getALengthArgument() + } + + final override predicate first(ControlFlowElement first) { + // First element of first length argument + first(this.getLengthArgument(0), first) + or + // No length argument: element itself + not exists(this.getLengthArgument(0)) and + first = this + } + + final override predicate last(ControlFlowElement last, Completion c) { + // Post-order: element itself (when no initializer) + last = this and + not this.hasInitializer() and + c.isValidFor(this) + or + // Last element of initializer + last(this.getInitializer(), last, c) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from self to first element of initializer + pred = this and + first(this.getInitializer(), succ) and + c instanceof SimpleCompletion + or + exists(int i | + last(this.getLengthArgument(i), pred, c) and + c instanceof SimpleCompletion + | + // Flow from last length argument to self + i = max(int j | exists(this.getLengthArgument(j))) and + succ = this + or + // Flow from one length argument to the next + first(this.getLengthArgument(i + 1), succ) + ) + } + } + + private class SwitchExprTree extends PostOrderTree, SwitchTree, SwitchExpr { + final override predicate propagatesAbnormal(ControlFlowElement child) { + SwitchTree.super.propagatesAbnormal(child) + or + child = this.getACase() + } + + final override predicate first(ControlFlowElement first) { first(expr, first) } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + SwitchTree.super.succ(pred, succ, c) + or + last(this.getACase(), pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + private class SwitchCaseExprTree extends PostOrderTree, CaseTree, SwitchCaseExpr { + final override predicate first(ControlFlowElement first) { first(pattern, first) } + + final override predicate last(ControlFlowElement last, Completion c) { + PostOrderTree.super.last(last, c) + or + // Last case exists with a non-match + exists(SwitchExpr se, int i, ConditionalCompletion cc | + this = se.getCase(i) and + not this.matchesAll() and + not exists(se.getCase(i + 1)) and + last([pattern, this.getCondition()], last, cc) and + (cc.(MatchingCompletion).isNonMatch() or cc instanceof FalseCompletion) and + c = + any(NestedCompletion nc | + nc.getInnerCompletion() = cc and + nc + .getOuterCompletion() + .(ThrowCompletion) + .getExceptionClass() + .hasQualifiedName("System.InvalidOperationException") + ) + ) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + CaseTree.super.succ(pred, succ, c) + or + last(body, pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + private class ConstructorInitializerTree extends PostOrderTree, ConstructorInitializer { + private ControlFlowTree getChildElement(int i) { result = getExprChild(this, i) } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getChildElement(_) + } + + final override predicate first(ControlFlowElement first) { + first(this.getChildElement(0), first) + or + not exists(this.getChildElement(0)) and + first = this + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Post-order: flow from last element of last child to element itself + exists(int lst | + lst = max(int i | exists(this.getChildElement(i))) and + last(this.getChildElement(lst), pred, c) and + succ = this and + c instanceof NormalCompletion + ) + or + // Standard left-to-right evaluation + exists(int i | + last(this.getChildElement(i), pred, c) and + c instanceof NormalCompletion and + first(this.getChildElement(i + 1), succ) + ) + or + exists(Constructor con | + last(this, pred, c) and + con = this.getConstructor() and + c instanceof NormalCompletion + | + // Flow from constructor initializer to first member initializer + exists(InitializerSplitting::InitializedInstanceMember m | + InitializerSplitting::constructorInitializeOrder(con, m, 0) + | + first(m.getInitializer(), succ) ) - ) - ) - or - cfe = - any(TryStmt ts | - result = getBlockOrCatchFinallyPred(ts, c) and + or + // Flow from constructor initializer to first element of constructor body + not InitializerSplitting::constructorInitializeOrder(con, _, _) and + first(con.getBody(), succ) + ) + } + } +} + +module Statements { + private class StandardStmt extends StandardElement, PreOrderTree, Stmt { + StandardStmt() { + // The following statements need special treatment + not this instanceof IfStmt and + not this instanceof SwitchStmt and + (this instanceof DefaultCase or not this instanceof CaseStmt) and + not this instanceof LoopStmt and + not this instanceof TryStmt and + not this instanceof SpecificCatchClause and + not this instanceof JumpStmt + } + + final override ControlFlowTree getChildElement(int i) { + not this instanceof GeneralCatchClause and + not this instanceof FixedStmt and + not this instanceof UsingBlockStmt and + not this instanceof DefaultCase and + result = this.getChild(i) + or + this = any(GeneralCatchClause gcc | i = 0 and result = gcc.getBlock()) + or + this = + any(FixedStmt fs | + result = fs.getVariableDeclExpr(i) + or + result = fs.getBody() and + i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 + ) + or + this = + any(UsingBlockStmt us | + if exists(us.getExpr()) + then ( + result = us.getExpr() and + i = 0 + or + result = us.getBody() and + i = 1 + ) else ( + result = us.getVariableDeclExpr(i) + or + result = us.getBody() and + i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 + ) + ) + or + result = this.(DefaultCase).getStmt() and + i = 0 + } + + final override predicate last(ControlFlowElement last, Completion c) { + last(this.getLastChild(), last, c) + or + this.isLeafElement() and + last = this and + c.isValidFor(this) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + StandardElement.super.succ(pred, succ, c) + or + pred = this and + first(this.getFirstChild(), succ) and + c instanceof SimpleCompletion + } + } + + private class IfStmtTree extends PreOrderTree, IfStmt { + private Expr condition; + + IfStmtTree() { condition = this.getCondition() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { child = condition } + + final override predicate last(ControlFlowElement last, Completion c) { + // Condition exits with a false completion and there is no `else` branch + last(condition, last, c) and + c instanceof FalseCompletion and + not exists(this.getElse()) + or + // Then branch exits with any completion + last(this.getThen(), last, c) + or + // Else branch exits with any completion + last(this.getElse(), last, c) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Pre-order: flow from statement itself to first element of condition + pred = this and + first(condition, succ) and + c instanceof SimpleCompletion + or + last(condition, pred, c) and + ( + // Flow from last element of condition to first element of then branch + c instanceof TrueCompletion and first(this.getThen(), succ) + or + // Flow from last element of condition to first element of else branch + c instanceof FalseCompletion and first(this.getElse(), succ) + ) + } + } + + private class SwitchStmtTree extends PreOrderTree, SwitchTree, SwitchStmt { + final override predicate last(ControlFlowElement last, Completion c) { + // Switch expression exits normally and there are no cases + not exists(this.getACase()) and + last(expr, last, c) and + c instanceof NormalCompletion + or + // A statement exits with a `break` completion + last(this.getStmt(_), last, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + or + // A statement exits abnormally + last(this.getStmt(_), last, c) and + not c instanceof BreakCompletion and + not c instanceof NormalCompletion and + not getLabledStmt(c.(GotoCompletion).getLabel(), this.getEnclosingCallable()) instanceof + CaseStmt + or + // Last case exits with a non-match + exists(CaseStmt cs, int last_ | + last_ = max(int i | exists(this.getCase(i))) and + cs = this.getCase(last_) + | + last(cs.getPattern(), last, c) and + not c.(MatchingCompletion).isMatch() + or + last(cs.getCondition(), last, c) and + c instanceof FalseCompletion + ) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + SwitchTree.super.succ(pred, succ, c) + or + // Pre-order: flow from statement itself to first switch expression + pred = this and + first(expr, succ) and + c instanceof SimpleCompletion + or + // Flow from last element of non-`case` statement `i` to first element of statement `i+1` + exists(int i | last(this.getStmt(i), pred, c) | + not this.getStmt(i) instanceof CaseStmt and + c instanceof NormalCompletion and + first(this.getStmt(i + 1), succ) + ) + or + // Flow from last element of `case` statement `i` to first element of statement `i+1` + exists(int i | last(this.getStmt(i).(CaseStmt).getBody(), pred, c) | + c instanceof NormalCompletion and + first(this.getStmt(i + 1), succ) + ) + } + } + + private class CaseStmtTree extends PreOrderTree, CaseTree, CaseStmt { + final override predicate last(ControlFlowElement last, Completion c) { + // Condition exists with a `false` completion + last(this.getCondition(), last, c) and + c instanceof FalseCompletion + or + // Case pattern exits with a non-match + last(pattern, last, c) and + not c.(MatchingCompletion).isMatch() + or + // Case body exits with any completion + last(body, last, c) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + CaseTree.super.succ(pred, succ, c) + or + pred = this and + first(pattern, succ) and + c instanceof SimpleCompletion + } + } + + abstract private class LoopStmtTree extends PreOrderTree, LoopStmt { + Stmt body; + + LoopStmtTree() { body = this.getBody() } + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getCondition() + } + + final override predicate last(ControlFlowElement last, Completion c) { + // Condition exits with a false completion + last(this.getCondition(), last, c) and + c instanceof FalseCompletion + or + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + last(body, last, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + or + // Body exits with a completion that does not continue the loop + last(body, last, c) and + not c instanceof BreakCompletion and + not c.continuesLoop() + } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of condition to first element of loop body + last(this.getCondition(), pred, c) and + c instanceof TrueCompletion and + first(body, succ) + or + // Flow from last element of loop body back to first element of condition + not this instanceof ForStmt and + last(body, pred, c) and + c.continuesLoop() and + first(this.getCondition(), succ) + } + } + + private class WhileStmtTree extends LoopStmtTree, WhileStmt { + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + LoopStmtTree.super.succ(pred, succ, c) + or + pred = this and + first(this.getCondition(), succ) and + c instanceof SimpleCompletion + } + } + + private class DoStmtTree extends LoopStmtTree, DoStmt { + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + LoopStmtTree.super.succ(pred, succ, c) + or + pred = this and + first(body, succ) and + c instanceof SimpleCompletion + } + } + + private class ForStmtTree extends LoopStmtTree, ForStmt { + /** Gets the condition if it exists, otherwise the body. */ + private ControlFlowElement getConditionOrBody() { + result = this.getCondition() + or + not exists(this.getCondition()) and + result = body + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + LoopStmtTree.super.succ(pred, succ, c) + or + // Pre-order: flow from statement itself to first element of first initializer/ + // condition/loop body + exists(ControlFlowElement next | + pred = this and + first(next, succ) and + c instanceof SimpleCompletion + | + next = this.getInitializer(0) + or + not exists(this.getInitializer(0)) and + next = this.getConditionOrBody() + ) + or + // Flow from last element of initializer `i` to first element of initializer `i+1` + exists(int i | last(this.getInitializer(i), pred, c) | + c instanceof NormalCompletion and + first(this.getInitializer(i + 1), succ) + ) + or + // Flow from last element of last initializer to first element of condition/loop body + exists(int last | last = max(int i | exists(this.getInitializer(i))) | + last(this.getInitializer(last), pred, c) and + c instanceof NormalCompletion and + first(this.getConditionOrBody(), succ) + ) + or + // Flow from last element of condition into first element of loop body + last(this.getCondition(), pred, c) and + c instanceof TrueCompletion and + first(body, succ) + or + // Flow from last element of loop body to first element of update/condition/self + exists(ControlFlowElement next | + last(body, pred, c) and + c.continuesLoop() and + first(next, succ) and + if exists(this.getUpdate(0)) + then next = this.getUpdate(0) + else next = this.getConditionOrBody() + ) + or + // Flow from last element of update to first element of next update/condition/loop body + exists(ControlFlowElement next, int i | + last(this.getUpdate(i), pred, c) and + c instanceof NormalCompletion and + first(next, succ) and + if exists(this.getUpdate(i + 1)) + then next = this.getUpdate(i + 1) + else next = this.getConditionOrBody() + ) + } + } + + private class ForeachStmtTree extends ControlFlowTree, ForeachStmt { + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getIterableExpr() + } + + final override predicate first(ControlFlowElement first) { + // Unlike most other statements, `foreach` statements are not modelled in + // pre-order, because we use the `foreach` node itself to represent the + // emptiness test that determines whether to execute the loop body + first(this.getIterableExpr(), first) + } + + final override predicate last(ControlFlowElement last, Completion c) { + // Emptiness test exits with no more elements + last = this and + c.(EmptinessCompletion).isEmpty() + or + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + last(this.getBody(), last, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + or + // Body exits abnormally + last(this.getBody(), last, c) and + not c instanceof NormalCompletion and + not c instanceof ContinueCompletion and + not c instanceof BreakCompletion + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from last element of iterator expression to emptiness test + last(this.getIterableExpr(), pred, c) and + c instanceof NormalCompletion and + succ = this + or + // Flow from emptiness test to first element of variable declaration/loop body + pred = this and + c = any(EmptinessCompletion ec | not ec.isEmpty()) and + ( + first(this.getVariableDeclExpr(), succ) + or + first(this.getVariableDeclTuple(), succ) + or + not exists(this.getVariableDeclExpr()) and + not exists(this.getVariableDeclTuple()) and + first(this.getBody(), succ) + ) + or + // Flow from last element of variable declaration to first element of loop body + ( + last(this.getVariableDeclExpr(), pred, c) or + last(this.getVariableDeclTuple(), pred, c) + ) and + c instanceof SimpleCompletion and + first(this.getBody(), succ) + or + // Flow from last element of loop body back to emptiness test + last(this.getBody(), pred, c) and + c.continuesLoop() and + succ = this + } + } + + pragma[nomagic] + private ControlFlowElement lastLastCatchClause(CatchClause cc, Completion c) { + cc.isLast() and + last(cc, result, c) + } + + pragma[nomagic] + private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { + last(cc.getBlock(), result, c) + } + + class TryStmtTree extends PreOrderTree, TryStmt { + ControlFlowTree body; + + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getFinally() + } + + /** Holds if `last` is a last element of the block of this `try` statement. */ + pragma[nomagic] + predicate lastBlock(ControlFlowElement last, Completion c) { last(this.getBlock(), last, c) } + + /** + * Gets a last element from a `try` or `catch` block of this `try` statement + * that may finish with completion `c`, such that control may be transferred + * to the `finally` block (if it exists). + */ + pragma[nomagic] + ControlFlowElement getBlockOrCatchFinallyPred(Completion c) { + this.lastBlock(result, c) and + ( + // Any non-throw completion from the `try` block will always continue directly + // to the `finally` block + not c instanceof ThrowCompletion + or + // Any completion from the `try` block will continue to the `finally` block + // when there are no catch clauses + not exists(this.getACatchClause()) + ) + or + // Last element from any of the `catch` clause blocks continues to the `finally` block + result = lastCatchClauseBlock(this.getACatchClause(), c) + or + // Last element of last `catch` clause continues to the `finally` block + result = lastLastCatchClause(this.getACatchClause(), c) + } + + pragma[nomagic] + private predicate lastFinally0(ControlFlowElement last, Completion c) { + last(this.getFinally(), last, c) + } + + pragma[nomagic] + private predicate lastFinally( + ControlFlowElement last, NormalCompletion finally, Completion outer + ) { + this.lastFinally0(last, finally) and + exists(this.getBlockOrCatchFinallyPred(any(Completion c0 | outer = c0.getOuterCompletion()))) + } + + final override predicate last(ControlFlowElement last, Completion c) { + last = this.getBlockOrCatchFinallyPred(c) and ( // If there is no `finally` block, last elements are from the body, from // the blocks of one of the `catch` clauses, or from the last `catch` clause - not ts.hasFinally() + not this.hasFinally() or // Exit completions ignore the `finally` block c instanceof ExitCompletion ) or - result = lastTryStmtFinally(ts, c, any(NormalCompletion nc)) + this.lastFinally(last, c, any(NormalCompletion nc)) or // If the `finally` block completes normally, it inherits any non-normal // completion that was current before the `finally` block was entered - exists(NormalCompletion finally, Completion outer | - result = lastTryStmtFinally(ts, finally, outer) - | + exists(NormalCompletion finally, Completion outer | this.lastFinally(last, finally, outer) | c = any(NestedCompletion nc | nc.getInnerCompletion() = finally and nc.getOuterCompletion() = outer @@ -873,685 +1384,181 @@ ControlFlowElement last(ControlFlowElement cfe, Completion c) { not finally instanceof ConditionalCompletion and c = outer ) - ) -} + } -/** - * Gets a potential last element executed within control flow element `cfe`, - * as well as its completion, where the last element of `cfe` is recursively - * computed as specified by `rec`. - */ -pragma[nomagic] -private ControlFlowElement lastRec(TLastRecComputation rec, ControlFlowElement cfe, Completion c) { - result = last(lastNonRec(cfe, TRec(rec)), c) -} + /** + * Gets an exception type that is thrown by `cfe` in the block of this `try` + * statement. Throw completion `c` matches the exception type. + */ + ExceptionClass getAThrownException(ControlFlowElement cfe, ThrowCompletion c) { + this.lastBlock(cfe, c) and + result = c.getExceptionClass() + } -pragma[nomagic] -private ControlFlowElement lastRecSpecific(ControlFlowElement cfe, Completion c1, Completion c2) { - result = lastRec(TLastRecSpecificCompletion(c2), cfe, c1) -} - -pragma[nomagic] -private ControlFlowElement lastTryStmtBlock(TryStmt ts, Completion c) { - result = last(ts.getBlock(), c) -} - -pragma[nomagic] -private ControlFlowElement lastLastCatchClause(CatchClause cc, Completion c) { - cc.isLast() and - result = last(cc, c) -} - -pragma[nomagic] -private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { - result = last(cc.getBlock(), c) -} - -private ControlFlowElement lastSpecificCatchClauseFilterClause(SpecificCatchClause scc, Completion c) { - result = last(scc.getFilterClause(), c) -} - -/** - * Gets a last element from a `try` or `catch` block of this `try` statement - * that may finish with completion `c`, such that control may be transferred - * to the `finally` block (if it exists). - */ -pragma[nomagic] -private ControlFlowElement getBlockOrCatchFinallyPred(TryStmt ts, Completion c) { - result = lastTryStmtBlock(ts, c) and - ( - // Any non-throw completion from the `try` block will always continue directly - // to the `finally` block - not c instanceof ThrowCompletion - or - // Any completion from the `try` block will continue to the `finally` block - // when there are no catch clauses - not exists(ts.getACatchClause()) - ) - or - // Last element from any of the `catch` clause blocks continues to the `finally` block - result = lastCatchClauseBlock(ts.getACatchClause(), c) - or - // Last element of last `catch` clause continues to the `finally` block - result = lastLastCatchClause(ts.getACatchClause(), c) -} - -pragma[nomagic] -private ControlFlowElement lastTryStmtFinally0(TryStmt ts, Completion c) { - result = last(ts.getFinally(), c) -} - -pragma[nomagic] -ControlFlowElement lastTryStmtFinally(TryStmt ts, NormalCompletion finally, Completion outer) { - result = lastTryStmtFinally0(ts, finally) and - exists(getBlockOrCatchFinallyPred(ts, any(Completion c0 | outer = c0.getOuterCompletion()))) -} - -/** - * Holds if the `try` block that catch clause `last` belongs to may throw an - * exception of type `c`, where no `catch` clause is guaranteed to catch it. - * The catch clause `last` is the last catch clause in the `try` statement - * that it belongs to. - */ -pragma[nomagic] -private predicate throwMayBeUncaught(SpecificCatchClause last, ThrowCompletion c) { - exists(TryStmt ts | - ts = last.getTryStmt() and - exists(lastTryStmtBlock(ts, c)) and - not ts.getACatchClause() instanceof GeneralCatchClause and - forall(SpecificCatchClause scc | scc = ts.getACatchClause() | - scc.hasFilterClause() - or - not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() - ) and - last.isLast() - ) -} - -/** - * Gets a control flow successor for control flow element `cfe`, given that - * `cfe` finishes with completion `c`. - */ -pragma[nomagic] -ControlFlowElement succ(ControlFlowElement cfe, Completion c) { - // Pre-order: flow from element itself to first element of first child - cfe = - any(StandardStmt ss | - result = first(ss.getFirstChildElement()) and - c instanceof SimpleCompletion - ) - or - // Post-order: flow from last element of last child to element itself - cfe = last(result.(StandardExpr).getLastChildElement(), c) and - c instanceof NormalCompletion - or - // Standard left-to-right evaluation - exists(StandardElement parent, int i | - cfe = last(parent.(StandardElement).getNonLastChildElement(i), c) and - c instanceof NormalCompletion and - result = first(parent.getChildElement(i + 1)) - ) - or - // Post-order: flow from last element of operand to element itself - result = - any(LogicalNotExpr lne | - cfe = last(lne.getOperand(), c.(BooleanCompletion).getDual()) - or - cfe = last(lne.getOperand(), c) and - c instanceof SimpleCompletion - ) - or - exists(LogicalAndExpr lae | - // Flow from last element of left operand to first element of right operand - cfe = last(lae.getLeftOperand(), c) and - c instanceof TrueCompletion and - result = first(lae.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(lae.getLeftOperand(), c) and - c instanceof FalseCompletion and - result = lae - or - // Post-order: flow from last element of right operand to element itself - cfe = last(lae.getRightOperand(), c) and - c instanceof NormalCompletion and - result = lae - ) - or - exists(LogicalOrExpr loe | - // Flow from last element of left operand to first element of right operand - cfe = last(loe.getLeftOperand(), c) and - c instanceof FalseCompletion and - result = first(loe.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(loe.getLeftOperand(), c) and - c instanceof TrueCompletion and - result = loe - or - // Post-order: flow from last element of right operand to element itself - cfe = last(loe.getRightOperand(), c) and - c instanceof NormalCompletion and - result = loe - ) - or - exists(NullCoalescingExpr nce | - // Flow from last element of left operand to first element of right operand - cfe = last(nce.getLeftOperand(), c) and - c.(NullnessCompletion).isNull() and - result = first(nce.getRightOperand()) - or - // Post-order: flow from last element of left operand to element itself - cfe = last(nce.getLeftOperand(), c) and - result = nce and - c instanceof NormalCompletion and - not c.(NullnessCompletion).isNull() - or - // Post-order: flow from last element of right operand to element itself - cfe = last(nce.getRightOperand(), c) and - c instanceof NormalCompletion and - result = nce - ) - or - exists(ConditionalExpr ce | - // Flow from last element of condition to first element of then branch - cfe = last(ce.getCondition(), c) and - c instanceof TrueCompletion and - result = first(ce.getThen()) - or - // Flow from last element of condition to first element of else branch - cfe = last(ce.getCondition(), c) and - c instanceof FalseCompletion and - result = first(ce.getElse()) - or - // Post-order: flow from last element of a branch to element itself - cfe = last([ce.getThen(), ce.getElse()], c) and - c instanceof NormalCompletion and - result = ce - ) - or - exists(ConditionallyQualifiedExpr parent, int i | - cfe = last(getExprChildElement(parent, i), c) and - c instanceof NormalCompletion and - if i = 0 then c.(NullnessCompletion).isNonNull() else any() - | - // Post-order: flow from last element of last child to element itself - i = max(int j | exists(getExprChildElement(parent, j))) and - result = parent - or - // Standard left-to-right evaluation - result = first(getExprChildElement(parent, i + 1)) - ) - or - // Post-order: flow from last element of thrown expression to expression itself - cfe = last(result.(ThrowExpr).getExpr(), c) and - c instanceof NormalCompletion - or - exists(ObjectCreation oc | - // Flow from last element of argument `i` to first element of argument `i+1` - exists(int i | cfe = last(getObjectCreationArgument(oc, i), c) | - result = first(getObjectCreationArgument(oc, i + 1)) and - c instanceof NormalCompletion - ) - or - // Flow from last element of last argument to self - exists(int last | last = max(int i | exists(getObjectCreationArgument(oc, i))) | - cfe = last(getObjectCreationArgument(oc, last), c) and - result = oc and - c instanceof NormalCompletion - ) - or - // Flow from self to first element of initializer - cfe = oc and - result = first(oc.getInitializer()) and - c instanceof SimpleCompletion - ) - or - exists(ArrayCreation ac | - // Flow from self to first element of initializer - cfe = ac and - result = first(ac.getInitializer()) and - c instanceof SimpleCompletion - or - exists(int i | - cfe = last(ac.getLengthArgument(i), c) and - c instanceof SimpleCompletion - | - // Flow from last length argument to self - i = max(int j | exists(ac.getLengthArgument(j))) and - result = ac - or - // Flow from one length argument to the next - result = first(ac.getLengthArgument(i + 1)) - ) - ) - or - exists(IfStmt is | - // Pre-order: flow from statement itself to first element of condition - cfe = is and - result = first(is.getCondition()) and - c instanceof SimpleCompletion - or - cfe = last(is.getCondition(), c) and - ( - // Flow from last element of condition to first element of then branch - c instanceof TrueCompletion and result = first(is.getThen()) - or - // Flow from last element of condition to first element of else branch - c instanceof FalseCompletion and result = first(is.getElse()) - ) - ) - or - exists(Switch s | - // Flow from last element of switch expression to first element of first case - cfe = last(s.getExpr(), c) and - c instanceof NormalCompletion and - result = first(s.getCase(0)) - or - // Flow from last element of case pattern to next case - exists(Case case, int i | case = s.getCase(i) | - cfe = last(case.getPattern(), c) and - c.(MatchingCompletion).isNonMatch() and - result = first(s.getCase(i + 1)) - ) - or - // Flow from last element of condition to next case - exists(Case case, int i | case = s.getCase(i) | - cfe = last(case.getCondition(), c) and - c instanceof FalseCompletion and - result = first(s.getCase(i + 1)) - ) - ) - or - exists(SwitchStmt ss | - // Pre-order: flow from statement itself to first switch expression - cfe = ss and - result = first(ss.getExpr()) and - c instanceof SimpleCompletion - or - // Flow from last element of non-`case` statement `i` to first element of statement `i+1` - exists(int i | cfe = last(ss.getStmt(i), c) | - not ss.getStmt(i) instanceof CaseStmt and - c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) - ) - or - // Flow from last element of `case` statement `i` to first element of statement `i+1` - exists(int i | cfe = last(ss.getStmt(i).(CaseStmt).getBody(), c) | - c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) - ) - ) - or - // Post-order: flow from last element of a case to element itself - cfe = last(result.(SwitchExpr).getACase(), c) and - c instanceof NormalCompletion - or - exists(Case case | - cfe = last(case.getPattern(), c) and - c.(MatchingCompletion).isMatch() and - ( - if exists(case.getCondition()) - then - // Flow from the last element of pattern to the condition - result = first(case.getCondition()) - else - // Flow from last element of pattern to first element of body - result = first(case.getBody()) - ) - or - // Flow from last element of condition to first element of body - cfe = last(case.getCondition(), c) and - c instanceof TrueCompletion and - result = first(case.getBody()) - ) - or - // Pre-order: flow from case itself to first element of pattern - result = first(cfe.(CaseStmt).getPattern()) and - c instanceof SimpleCompletion - or - // Post-order: flow from last element of a case body to element itself - cfe = last(result.(SwitchCaseExpr).getBody(), c) and - c instanceof NormalCompletion - or - // Pre-order: flow from statement itself to first element of statement - cfe = - any(DefaultCase dc | - result = first(dc.getStmt()) and - c instanceof SimpleCompletion - ) - or - exists(LoopStmt ls | - // Flow from last element of condition to first element of loop body - cfe = last(ls.getCondition(), c) and - c instanceof TrueCompletion and - result = first(ls.getBody()) - or - // Flow from last element of loop body back to first element of condition - not ls instanceof ForStmt and - cfe = last(ls.getBody(), c) and - c.continuesLoop() and - result = first(ls.getCondition()) - ) - or - cfe = - any(WhileStmt ws | - // Pre-order: flow from statement itself to first element of condition - result = first(ws.getCondition()) and - c instanceof SimpleCompletion - ) - or - cfe = - any(DoStmt ds | + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { // Pre-order: flow from statement itself to first element of body - result = first(ds.getBody()) and + pred = this and + first(this.getBlock(), succ) and c instanceof SimpleCompletion - ) - or - exists(ForStmt fs | - // Pre-order: flow from statement itself to first element of first initializer/ - // condition/loop body - exists(ControlFlowElement next | - cfe = fs and - result = first(next) and - c instanceof SimpleCompletion - | - next = fs.getInitializer(0) or - not exists(fs.getInitializer(0)) and - next = getForStmtConditionOrBody(fs) - ) - or - // Flow from last element of initializer `i` to first element of initializer `i+1` - exists(int i | cfe = last(fs.getInitializer(i), c) | - c instanceof NormalCompletion and - result = first(fs.getInitializer(i + 1)) - ) - or - // Flow from last element of last initializer to first element of condition/loop body - exists(int last | last = max(int i | exists(fs.getInitializer(i))) | - cfe = last(fs.getInitializer(last), c) and - c instanceof NormalCompletion and - result = first(getForStmtConditionOrBody(fs)) - ) - or - // Flow from last element of condition into first element of loop body - cfe = last(fs.getCondition(), c) and - c instanceof TrueCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body to first element of update/condition/self - exists(ControlFlowElement next | - cfe = last(fs.getBody(), c) and - c.continuesLoop() and - result = first(next) and - if exists(fs.getUpdate(0)) - then next = fs.getUpdate(0) - else next = getForStmtConditionOrBody(fs) - ) - or - // Flow from last element of update to first element of next update/condition/loop body - exists(ControlFlowElement next, int i | - cfe = last(fs.getUpdate(i), c) and - c instanceof NormalCompletion and - result = first(next) and - if exists(fs.getUpdate(i + 1)) - then next = fs.getUpdate(i + 1) - else next = getForStmtConditionOrBody(fs) - ) - ) - or - exists(ForeachStmt fs | - // Flow from last element of iterator expression to emptiness test - cfe = last(fs.getIterableExpr(), c) and - c instanceof NormalCompletion and - result = fs - or - // Flow from emptiness test to first element of variable declaration/loop body - cfe = fs and - c = any(EmptinessCompletion ec | not ec.isEmpty()) and - ( - result = first(fs.getVariableDeclExpr()) + // Flow from last element of body to first `catch` clause + exists(this.getAThrownException(pred, c)) and + first(this.getCatchClause(0), succ) or - result = first(fs.getVariableDeclTuple()) - or - not exists(fs.getVariableDeclExpr()) and - not exists(fs.getVariableDeclTuple()) and - result = first(fs.getBody()) - ) - or - // Flow from last element of variable declaration to first element of loop body - ( - cfe = last(fs.getVariableDeclExpr(), c) or - cfe = last(fs.getVariableDeclTuple(), c) - ) and - c instanceof SimpleCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body back to emptiness test - cfe = last(fs.getBody(), c) and - c.continuesLoop() and - result = fs - ) - or - exists(TryStmt ts | - // Pre-order: flow from statement itself to first element of body - cfe = ts and - result = first(ts.getBlock()) and - c instanceof SimpleCompletion - or - // Flow from last element of body to first `catch` clause - exists(getAThrownException(ts, cfe, c)) and - result = first(ts.getCatchClause(0)) - or - exists(CatchClause cc, int i | cc = ts.getCatchClause(i) | - cfe = cc and - cc = last(ts.getCatchClause(i), c) and - ( - // Flow from one `catch` clause to the next - result = first(ts.getCatchClause(i + 1)) and - c = any(MatchingCompletion mc | not mc.isMatch()) + exists(CatchClause cc, int i | cc = this.getCatchClause(i) | + pred = cc and + last(this.getCatchClause(i), cc, c) and + ( + // Flow from one `catch` clause to the next + first(this.getCatchClause(i + 1), succ) and + c = any(MatchingCompletion mc | not mc.isMatch()) + or + // Flow from last `catch` clause to first element of `finally` block + this.getCatchClause(i).isLast() and + first(this.getFinally(), succ) and + c instanceof ThrowCompletion // inherited from `try` block + ) or - // Flow from last `catch` clause to first element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block + last(this.getCatchClause(i), pred, c) and + last(cc.getFilterClause(), pred, _) and + ( + // Flow from last element of `catch` clause filter to next `catch` clause + first(this.getCatchClause(i + 1), succ) and + c instanceof FalseCompletion + or + // Flow from last element of `catch` clause filter, of last clause, to first + // element of `finally` block + this.getCatchClause(i).isLast() and + first(this.getFinally(), succ) and + c instanceof ThrowCompletion // inherited from `try` block + ) + or + // Flow from last element of a `catch` block to first element of `finally` block + pred = lastCatchClauseBlock(cc, c) and + first(this.getFinally(), succ) ) or - cfe = last(ts.getCatchClause(i), c) and - cfe = last(cc.getFilterClause(), _) and + // Flow from last element of `try` block to first element of `finally` block + this.lastBlock(pred, c) and + first(this.getFinally(), succ) and + not c instanceof ExitCompletion and + (c instanceof ThrowCompletion implies not exists(this.getACatchClause())) + } + } + + private class SpecificCatchClauseTree extends PreOrderTree, SpecificCatchClause { + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getFilterClause() + } + + pragma[nomagic] + private predicate lastFilterClause(ControlFlowElement last, Completion c) { + last(this.getFilterClause(), last, c) + } + + /** + * Holds if the `try` block that this catch clause belongs to may throw an + * exception of type `c`, where no `catch` clause is guaranteed to catch it. + * This catch clause is the last catch clause in the `try` statement that + * it belongs to. + */ + pragma[nomagic] + private predicate throwMayBeUncaught(ThrowCompletion c) { + exists(TryStmtTree ts | + ts = this.getTryStmt() and + ts.lastBlock(_, c) and + not ts.getACatchClause() instanceof GeneralCatchClause and + forall(SpecificCatchClause scc | scc = ts.getACatchClause() | + scc.hasFilterClause() + or + not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() + ) and + this.isLast() + ) + } + + final override predicate last(ControlFlowElement last, Completion c) { + // Last element of `catch` block + last(this.getBlock(), last, c) + or + not this.isLast() and ( - // Flow from last element of `catch` clause filter to next `catch` clause - result = first(ts.getCatchClause(i + 1)) and + // Incompatible exception type: clause itself + last = this and + c.(MatchingCompletion).isNonMatch() + or + // Incompatible filter + this.lastFilterClause(last, c) and c instanceof FalseCompletion - or - // Flow from last element of `catch` clause filter, of last clause, to first - // element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block ) or - // Flow from last element of a `catch` block to first element of `finally` block - cfe = lastCatchClauseBlock(cc, c) and - result = first(ts.getFinally()) - ) - or - // Flow from last element of `try` block to first element of `finally` block - cfe = lastTryStmtBlock(ts, c) and - result = first(ts.getFinally()) and - not c instanceof ExitCompletion and - (c instanceof ThrowCompletion implies not exists(ts.getACatchClause())) - ) - or - exists(SpecificCatchClause scc | - // Flow from catch clause to variable declaration/filter clause/block - cfe = scc and - c.(MatchingCompletion).isMatch() and - exists(ControlFlowElement next | result = first(next) | - if exists(scc.getVariableDeclExpr()) - then next = scc.getVariableDeclExpr() - else - if exists(scc.getFilterClause()) - then next = scc.getFilterClause() - else next = scc.getBlock() - ) - or - // Flow from variable declaration to filter clause/block - cfe = last(scc.getVariableDeclExpr(), c) and - c instanceof SimpleCompletion and - exists(ControlFlowElement next | result = first(next) | - if exists(scc.getFilterClause()) then next = scc.getFilterClause() else next = scc.getBlock() - ) - or - // Flow from filter to block - cfe = last(scc.getFilterClause(), c) and - c instanceof TrueCompletion and - result = first(scc.getBlock()) - ) - or - // Post-order: flow from last element of child to statement itself - cfe = last(result.(JumpStmt).getChild(0), c) and - c instanceof NormalCompletion - or - exists(ConstructorInitializer ci, Constructor con | - cfe = last(ci, c) and - con = ci.getConstructor() and - c instanceof NormalCompletion - | - // Flow from constructor initializer to first member initializer - exists(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(con, m, 0) - | - result = first(m.getInitializer()) - ) - or - // Flow from constructor initializer to first element of constructor body - not InitializerSplitting::constructorInitializeOrder(con, _, _) and - result = first(con.getBody()) - ) - or - exists(Constructor con, InitializerSplitting::InitializedInstanceMember m, int i | - cfe = last(m.getInitializer(), c) and - c instanceof NormalCompletion and - InitializerSplitting::constructorInitializeOrder(con, m, i) - | - // Flow from one member initializer to the next - exists(InitializerSplitting::InitializedInstanceMember next | - InitializerSplitting::constructorInitializeOrder(con, next, i + 1) and - result = first(next.getInitializer()) - ) - or - // Flow from last member initializer to constructor body - m = InitializerSplitting::lastConstructorInitializer(con) and - result = first(con.getBody()) - ) - or - // Flow from element with `goto` completion to first element of relevant - // target - c = - any(GotoCompletion gc | - cfe = last(_, gc) and - // Special case: when a `goto` happens inside a `try` statement with a - // `finally` block, flow does not go directly to the target, but instead - // to the `finally` block (and from there possibly to the target) - not cfe = getBlockOrCatchFinallyPred(any(TryStmt ts | ts.hasFinally()), _) and - result = first(getLabledStmt(gc.getLabel(), cfe.getEnclosingCallable())) - ) - or - // Standard left-to-right evaluation - exists(QualifiedWriteAccess qwa, int i | - cfe = last(getExprChildElement(qwa, i), c) and - c instanceof NormalCompletion and - result = first(getExprChildElement(qwa, i + 1)) - ) - or - exists(AccessorWrite aw | - // Standard left-to-right evaluation - exists(int i | - cfe = last(getExprChildElement(aw, i), c) and - c instanceof NormalCompletion and - result = first(getExprChildElement(aw, i + 1)) - ) - or - // Flow from last element of last child to first accessor call - cfe = last(getExprChildElement(aw, getLastChildElement(aw)), c) and - result = aw.getCall(0) and - c instanceof NormalCompletion - or - // Flow from one call to the next - exists(int i | cfe = aw.getCall(i) | - result = aw.getCall(i + 1) and - c.isValidFor(cfe) and + // Last `catch` clause inherits throw completions from the `try` block, + // when the clause does not match + this.isLast() and + c = + any(NestedCompletion nc | + this.throwMayBeUncaught(nc.getOuterCompletion().(ThrowCompletion)) and + ( + // Incompatible exception type: clause itself + last = this and + nc.getInnerCompletion() = + any(MatchingCompletion mc | + mc.isNonMatch() and + mc.isValidFor(this) + ) + or + // Incompatible filter + this.lastFilterClause(last, nc.getInnerCompletion().(FalseCompletion)) + ) + ) + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Flow from catch clause to variable declaration/filter clause/block + pred = this and + c.(MatchingCompletion).isMatch() and + exists(ControlFlowElement next | first(next, succ) | + if exists(this.getVariableDeclExpr()) + then next = this.getVariableDeclExpr() + else + if exists(this.getFilterClause()) + then next = this.getFilterClause() + else next = this.getBlock() + ) + or + // Flow from variable declaration to filter clause/block + last(this.getVariableDeclExpr(), pred, c) and + c instanceof SimpleCompletion and + exists(ControlFlowElement next | first(next, succ) | + if exists(this.getFilterClause()) + then next = this.getFilterClause() + else next = this.getBlock() + ) + or + // Flow from filter to block + last(this.getFilterClause(), pred, c) and + c instanceof TrueCompletion and + first(this.getBlock(), succ) + } + } + + private class JumpStmtTree extends PostOrderTree, JumpStmt { + final override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getChild(0) + } + + final override predicate first(ControlFlowElement first) { + first(this.getChild(0), first) + or + not exists(this.getChild(0)) and first = this + } + + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + last(this.getChild(0), pred, c) and + succ = this and c instanceof NormalCompletion - ) - or - // Post-order: flow from last call to element itself - exists(int last | last = max(int i | exists(aw.getCall(i))) | - cfe = aw.getCall(last) and - result = aw and - c.isValidFor(cfe) and - c instanceof NormalCompletion - ) - ) -} - -/** - * Gets an exception type that is thrown by `cfe` in the block of `try` statement - * `ts`. Throw completion `c` matches the exception type. - */ -ExceptionClass getAThrownException(TryStmt ts, ControlFlowElement cfe, ThrowCompletion c) { - cfe = lastTryStmtBlock(ts, c) and - result = c.getExceptionClass() -} - -/** - * Gets the condition of `for` loop `fs` if it exists, otherwise the body. - */ -private ControlFlowElement getForStmtConditionOrBody(ForStmt fs) { - result = fs.getCondition() - or - not exists(fs.getCondition()) and - result = fs.getBody() -} - -/** - * Gets the control flow element that is first executed when entering - * callable `c`. - */ -ControlFlowElement succEntry(@top_level_exprorstmt_parent p) { - p = - any(Callable c | - if exists(c.(Constructor).getInitializer()) - then result = first(c.(Constructor).getInitializer()) - else - if InitializerSplitting::constructorInitializes(c, _) - then - result = - first(any(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(c, m, 0) - ).getInitializer()) - else result = first(c.getBody()) - ) - or - expr_parent_top_level_adjusted(any(Expr e | result = first(e)), _, p) and - not p instanceof Callable and - not p instanceof InitializerSplitting::InitializedInstanceMember -} - -/** - * Gets the callable that is exited when `cfe` finishes with completion `c`, - * if any. - */ -Callable succExit(ControlFlowElement cfe, Completion c) { - cfe = last(result.getBody(), c) and - not c instanceof GotoCompletion - or - exists(InitializerSplitting::InitializedInstanceMember m | - m = InitializerSplitting::lastConstructorInitializer(result) and - cfe = last(m.getInitializer(), c) and - not result.hasBody() - ) + } + } } cached @@ -1625,13 +1632,13 @@ private module Cached { * Gets a first control flow element executed within `cfe`. */ cached - ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { result = first(cfe) } + ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { first(cfe, result) } /** * Gets a potential last control flow element executed within `cfe`. */ cached - ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { result = last(cfe, _) } + ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { last(cfe, result, _) } } import Cached diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll index c61e731e8ce..c4a144d7631 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll @@ -15,28 +15,28 @@ private import ControlFlowGraphImpl private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow private predicate startsBB(ControlFlowElement cfe) { - not cfe = succ(_, _) and + not succ(_, cfe, _) and ( - exists(succ(cfe, _)) + succ(cfe, _, _) or - exists(succExit(cfe, _)) + succExit(cfe, _, _) ) or - strictcount(ControlFlowElement pred, Completion c | cfe = succ(pred, c)) > 1 + strictcount(ControlFlowElement pred, Completion c | succ(pred, cfe, c)) > 1 or exists(ControlFlowElement pred, int i | - cfe = succ(pred, _) and - i = count(ControlFlowElement succ, Completion c | succ = succ(pred, c)) + succ(pred, cfe, _) and + i = count(ControlFlowElement succ, Completion c | succ(pred, succ, c)) | i > 1 or i = 1 and - exists(succExit(pred, _)) + succExit(pred, _, _) ) } private predicate intraBBSucc(ControlFlowElement pred, ControlFlowElement succ) { - succ = succ(pred, _) and + succ(pred, succ, _) and not startsBB(succ) } @@ -45,7 +45,7 @@ private predicate bbIndex(ControlFlowElement bbStart, ControlFlowElement cfe, in private predicate succBB(PreBasicBlock pred, PreBasicBlock succ) { succ = pred.getASuccessor() } -private predicate entryBB(PreBasicBlock bb) { bb = succEntry(_) } +private predicate entryBB(PreBasicBlock bb) { succEntry(_, bb) } private predicate bbIDominates(PreBasicBlock dom, PreBasicBlock bb) = idominance(entryBB/1, succBB/2)(_, dom, bb) @@ -54,7 +54,7 @@ class PreBasicBlock extends ControlFlowElement { PreBasicBlock() { startsBB(this) } PreBasicBlock getASuccessorByType(SuccessorType t) { - result = succ(this.getLastElement(), any(Completion c | t.matchesCompletion(c))) + succ(this.getLastElement(), result, any(Completion c | t.matchesCompletion(c))) } PreBasicBlock getASuccessor() { result = this.getASuccessorByType(_) } @@ -98,9 +98,9 @@ class ConditionBlock extends PreBasicBlock { strictcount(Completion c | c = getConditionalCompletion(_) and ( - exists(succ(this.getLastElement(), c)) + succ(this.getLastElement(), _, c) or - exists(succExit(this.getLastElement(), c)) + succExit(this.getLastElement(), _, c) ) ) > 1 } @@ -109,12 +109,12 @@ class ConditionBlock extends PreBasicBlock { exists(ControlFlowElement last, Completion c | last = this.getLastElement() and c = getConditionalCompletion(cc) and - succ = succ(last, c) and + succ(last, succ, c) and // In the pre-CFG, we need to account for case where one predecessor node has // two edges to the same successor node. Assertion expressions are examples of // such nodes. not exists(Completion other | - succ = succ(last, other) and + succ(last, succ, other) and other != c ) and forall(PreBasicBlock pred | pred = succ.getAPredecessor() and pred != this | diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll index 3f637256d69..1599de3f6b6 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreSsa.qll @@ -15,6 +15,21 @@ private import PreBasicBlocks private import ControlFlowGraphImpl private import semmle.code.csharp.controlflow.Guards as Guards +pragma[noinline] +private predicate assignableNoCapturing(Assignable a, Callable c) { + exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable()) and + forall(AssignableDefinition def | def.getTarget() = a | + c = def.getEnclosingCallable() + or + def.getEnclosingCallable() instanceof Constructor + ) +} + +pragma[noinline] +private predicate assignableNoComplexQualifiers(Assignable a) { + forall(QualifiableExpr qe | qe.(AssignableAccess).getTarget() = a | qe.targetIsThisInstance()) +} + /** * A simple assignable. Either a local scope variable or a field/property * that behaves like a local scope variable. @@ -30,15 +45,8 @@ class SimpleAssignable extends Assignable { or this = any(TrivialProperty tp | not tp.isOverridableOrImplementable()) ) and - forall(AssignableDefinition def | def.getTarget() = this | - c = def.getEnclosingCallable() - or - def.getEnclosingCallable() instanceof Constructor - ) and - exists(AssignableAccess aa | aa.getTarget() = this | c = aa.getEnclosingCallable()) and - forall(QualifiableExpr qe | qe.(AssignableAccess).getTarget() = this | - qe.targetIsThisInstance() - ) + assignableNoCapturing(this, c) and + assignableNoComplexQualifiers(this) } /** Gets a callable in which this simple assignable can be analyzed. */ @@ -132,7 +140,7 @@ class Definition extends TPreSsaDef { predicate implicitEntryDef(Callable c, PreBasicBlock bb, SimpleAssignable a) { not a instanceof LocalScopeVariable and c = a.getACallable() and - bb = succEntry(c) + succEntry(c, bb) } private predicate assignableDefAt( @@ -149,7 +157,7 @@ private predicate assignableDefAt( or def.(ImplicitParameterDefinition).getParameter() = a and exists(Callable c | a = c.getAParameter() | - bb = succEntry(c) and + succEntry(c, bb) and i = -1 ) } @@ -161,7 +169,7 @@ private predicate readAt(PreBasicBlock bb, int i, AssignableRead read, SimpleAss pragma[noinline] private predicate exitBlock(PreBasicBlock bb, Callable c) { - exists(succExit(bb.getLastElement(), _)) and + succExit(bb.getLastElement(), _, _) and c = bb.getEnclosingCallable() } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll index 5e5e227a43c..551de92e73b 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll @@ -55,7 +55,7 @@ private module Cached { cached newtype TSplits = TSplitsNil() or - TSplitsCons(SplitInternal head, Splits tail) { + TSplitsCons(SplitImpl head, Splits tail) { exists( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk | @@ -71,7 +71,7 @@ private module Cached { splits = TSplitsNil() and result = "" or - exists(SplitInternal head, Splits tail, string headString, string tailString | + exists(SplitImpl head, Splits tail, string headString, string tailString | splits = TSplitsCons(head, tail) | headString = head.toString() and @@ -92,7 +92,7 @@ private import Cached * A split for a control flow element. For example, a tag that determines how to * continue execution after leaving a `finally` block. */ -class SplitImpl extends TSplit { +class Split extends TSplit { /** Gets a textual representation of this split. */ string toString() { none() } } @@ -115,7 +115,7 @@ private predicate overlapping(Callable c, SplitKind sk1, SplitKind sk2) { */ abstract class SplitKind extends TSplitKind { /** Gets a split of this kind. */ - SplitInternal getASplit() { result.getKind() = this } + SplitImpl getASplit() { result.getKind() = this } /** Holds if some split of this kind applies to control flow element `cfe`. */ predicate appliesTo(ControlFlowElement cfe) { this.getASplit().appliesTo(cfe) } @@ -157,7 +157,7 @@ abstract class SplitKind extends TSplitKind { // This class only exists to not pollute the externally visible `Split` class // with internal helper predicates -abstract class SplitInternal extends SplitImpl { +abstract class SplitImpl extends Split { /** Gets the kind of this split. */ abstract SplitKind getKind(); @@ -165,39 +165,39 @@ abstract class SplitInternal extends SplitImpl { * Holds if this split is entered when control passes from `pred` to `succ` with * completion `c`. * - * Invariant: `hasEntry(pred, succ, c) implies succ = Successor::succ(pred, c)`. + * Invariant: `hasEntry(pred, succ, c) implies succ(pred, succ, c)`. */ abstract predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c); /** - * Holds if this split is entered when control passes from `c` to the entry point - * `succ`. + * Holds if this split is entered when control passes from `scope` to the entry point + * `first`. * - * Invariant: `hasEntry(c, succ) implies succ = Successor::succEntry(c)`. + * Invariant: `hasEntryScope(scope, first) implies succEntry(scope, first)`. */ - abstract predicate hasEntry(Callable c, ControlFlowElement succ); + abstract predicate hasEntryScope(Callable scope, ControlFlowElement first); /** * Holds if this split is left when control passes from `pred` to `succ` with * completion `c`. * - * Invariant: `hasExit(pred, succ, c) implies succ = Successor::succ(pred, c)`. + * Invariant: `hasExit(pred, succ, c) implies succ(pred, succ, c)`. */ abstract predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c); /** - * Holds if this split is left when control passes from `pred` out of the enclosing - * callable `result` with completion `c`. + * Holds if this split is left when control passes from `last` out of the enclosing + * scope `scope` with completion `c`. * - * Invariant: `succ = hasExit(pred, c) implies succ = Successor::succExit(pred, c)` + * Invariant: `hasExitScope(last, scope, c) implies succExit(last, scope, c)` */ - abstract Callable hasExit(ControlFlowElement pred, Completion c); + abstract predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c); /** * Holds if this split is maintained when control passes from `pred` to `succ` with * completion `c`. * - * Invariant: `hasSuccessor(pred, succ, c) implies succ = Successor::succ(pred, c)` + * Invariant: `hasSuccessor(pred, succ, c) implies succ(pred, succ, c)` */ abstract predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c); @@ -205,7 +205,7 @@ abstract class SplitInternal extends SplitImpl { final predicate appliesTo(ControlFlowElement cfe) { this.hasEntry(_, cfe, _) or - this.hasEntry(_, cfe) + this.hasEntryScope(_, cfe) or exists(ControlFlowElement pred | this.appliesTo(pred) | this.hasSuccessor(pred, cfe, _)) } @@ -331,10 +331,10 @@ module InitializerSplitting { * * respectively. */ - class InitializerSplitImpl extends SplitImpl, TInitializerSplit { + class InitializerSplit extends Split, TInitializerSplit { private Constructor c; - InitializerSplitImpl() { this = TInitializerSplit(c) } + InitializerSplit() { this = TInitializerSplit(c) } /** Gets the constructor. */ Constructor getConstructor() { result = c } @@ -352,40 +352,40 @@ module InitializerSplitting { int getNextListOrder() { result = 1 } - private class InitializerSplitInternal extends SplitInternal, InitializerSplitImpl { + private class InitializerSplitImpl extends SplitImpl, InitializerSplit { override InitializerSplitKind getKind() { any() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { exists(ConstructorInitializer ci | - pred = last(ci, c) and - succ = succ(pred, c) and + last(ci, pred, c) and + succ(pred, succ, c) and succ = any(InitializedInstanceMember m).getAnInitializerDescendant() and this.getConstructor() = ci.getConstructor() ) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { - succ = succEntry(c) and - c = this.getConstructor() and - succ = any(InitializedInstanceMember m).getAnInitializerDescendant() + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { + succEntry(scope, first) and + scope = this.getConstructor() and + first = any(InitializedInstanceMember m).getAnInitializerDescendant() } override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesTo(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and not succ = any(InitializedInstanceMember m).getAnInitializerDescendant() and succ.getEnclosingCallable() = this.getConstructor() } - override Callable hasExit(ControlFlowElement pred, Completion c) { - this.appliesTo(pred) and - result = succExit(pred, c) and - result = this.getConstructor() + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { + this.appliesTo(last) and + succExit(last, scope, c) and + scope = this.getConstructor() } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesTo(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and succ = any(InitializedInstanceMember m | constructorInitializes(this.getConstructor(), m)) .getAnInitializerDescendant() @@ -408,10 +408,10 @@ module ConditionalCompletionSplitting { * we record whether `x`, `y`, and `!y` evaluate to `true` or `false`, and restrict * the edges out of `!y` and `x && !y` accordingly. */ - class ConditionalCompletionSplitImpl extends SplitImpl, TConditionalCompletionSplit { + class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit { ConditionalCompletion completion; - ConditionalCompletionSplitImpl() { this = TConditionalCompletionSplit(completion) } + ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) } override string toString() { result = completion.toString() } } @@ -426,33 +426,32 @@ module ConditionalCompletionSplitting { int getNextListOrder() { result = InitializerSplitting::getNextListOrder() + 1 } - private class ConditionalCompletionSplitInternal extends SplitInternal, - ConditionalCompletionSplitImpl { + private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit { override ConditionalCompletionSplitKind getKind() { any() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - succ = succ(pred, c) and - exists(last(succ, completion)) and + succ(pred, succ, c) and + last(succ, _, completion) and ( - pred = last(succ.(LogicalNotExpr).getOperand(), c) and + last(succ.(LogicalNotExpr).getOperand(), pred, c) and completion.(BooleanCompletion).getDual() = c or - pred = last(succ.(LogicalAndExpr).getAnOperand(), c) and + last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and completion = c or - pred = last(succ.(LogicalOrExpr).getAnOperand(), c) and + last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and completion = c or succ = any(ConditionalExpr ce | - pred = last([ce.getThen(), ce.getElse()], c) and + last([ce.getThen(), ce.getElse()], pred, c) and completion = c ) or succ = any(NullCoalescingExpr nce | exists(Expr operand | - pred = last(operand, c) and + last(operand, pred, c) and completion = c | if c instanceof NullnessCompletion @@ -461,25 +460,25 @@ module ConditionalCompletionSplitting { ) ) or - pred = last(succ.(SwitchExpr).getACase(), c) and + last(succ.(SwitchExpr).getACase(), pred, c) and completion = c or - pred = last(succ.(SwitchCaseExpr).getBody(), c) and + last(succ.(SwitchCaseExpr).getBody(), pred, c) and completion = c ) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesTo(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and if c instanceof ConditionalCompletion then completion = c else any() } - override Callable hasExit(ControlFlowElement pred, Completion c) { - this.appliesTo(pred) and - result = succExit(pred, c) and + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { + this.appliesTo(last) and + succExit(last, scope, c) and if c instanceof ConditionalCompletion then completion = c else any() } @@ -513,12 +512,12 @@ module AssertionSplitting { * we record whether `i >= 0` evaluates to `true` or `false`, and restrict the * edges out of the assertion accordingly. */ - class AssertionSplitImpl extends SplitImpl, TAssertionSplit { + class AssertionSplit extends Split, TAssertionSplit { Assertion a; boolean success; int i; - AssertionSplitImpl() { this = TAssertionSplit(a, i, success) } + AssertionSplit() { this = TAssertionSplit(a, i, success) } /** Gets the assertion. */ Assertion getAssertion() { result = a } @@ -543,13 +542,13 @@ module AssertionSplitting { int getNextListOrder() { result = ConditionalCompletionSplitting::getNextListOrder() + 1 } - private class AssertionSplitInternal extends SplitInternal, AssertionSplitImpl { + private class AssertionSplitImpl extends SplitImpl, AssertionSplit { override AssertionSplitKind getKind() { any() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { exists(AssertMethod m | - pred = last(a.getExpr(i), c) and - succ = succ(pred, c) and + last(a.getExpr(i), pred, c) and + succ(pred, succ, c) and m = a.getAssertMethod() and // The assertion only succeeds when all asserted arguments succeeded, so // we only enter a "success" state after the last argument has succeeded. @@ -575,12 +574,12 @@ module AssertionSplitting { ) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesTo(pred) and pred = a and - succ = succ(pred, c) and + succ(pred, succ, c) and ( success = true and c instanceof NormalCompletion @@ -590,10 +589,10 @@ module AssertionSplitting { ) } - override Callable hasExit(ControlFlowElement pred, Completion c) { - this.appliesTo(pred) and - pred = a and - result = succExit(pred, c) and + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { + this.appliesTo(last) and + last = a and + succExit(last, scope, c) and ( success = true and c instanceof NormalCompletion @@ -605,7 +604,7 @@ module AssertionSplitting { override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesTo(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and succ = getAnAssertionDescendant(a) } } @@ -681,7 +680,7 @@ module FinallySplitting { TryStmt getTryStmt() { result = try } /** Holds if this node is the entry node in the `finally` block it belongs to. */ - predicate isEntryNode() { this = first(try.getFinally()) } + predicate isEntryNode() { first(try.getFinally(), this) } } /** A control flow element that does not belong to a `finally` block. */ @@ -709,11 +708,11 @@ module FinallySplitting { * normal execution of the `try` block (when `M()` returns `true`), and one * representing exceptional execution of the `try` block (when `M()` returns `false`). */ - class FinallySplitImpl extends SplitImpl, TFinallySplit { + class FinallySplit extends Split, TFinallySplit { private FinallySplitType type; private int nestLevel; - FinallySplitImpl() { this = TFinallySplit(type, nestLevel) } + FinallySplit() { this = TFinallySplit(type, nestLevel) } /** * Gets the type of this `finally` split, that is, how to continue execution after the @@ -761,10 +760,10 @@ module FinallySplitting { ) { succ.isEntryNode() and nestLevel = nestLevel(succ.getTryStmt()) and - succ = succ(pred, c) + succ(pred, succ, c) } - private class FinallySplitInternal extends SplitInternal, FinallySplitImpl { + private class FinallySplitImpl extends SplitImpl, FinallySplit { override FinallySplitKind getKind() { result.getNestLevel() = this.getNestLevel() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { @@ -772,7 +771,7 @@ module FinallySplitting { this.getType().isSplitForEntryCompletion(c) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } /** * Holds if this split applies to control flow element `pred`, where `pred` @@ -780,14 +779,14 @@ module FinallySplitting { */ private predicate appliesToPredecessor(ControlFlowElement pred) { this.appliesTo(pred) and - (exists(succ(pred, _)) or exists(succExit(pred, _))) + (succ(pred, _, _) or succExit(pred, _, _)) } pragma[noinline] private predicate exit0(ControlFlowElement pred, TryStmt try, int nestLevel, Completion c) { this.appliesToPredecessor(pred) and nestLevel = nestLevel(try) and - pred = last(try, c) + last(try, pred, c) } /** @@ -800,7 +799,7 @@ module FinallySplitting { exit0(pred, try, this.getNestLevel(), c) and type = this.getType() | - if pred = last(try.getFinally(), c) + if last(try.getFinally(), pred, c) then // Finally block can itself exit with completion `c`: either `c` must // match this split, `c` must be an abnormal completion, or this split @@ -848,7 +847,7 @@ module FinallySplitting { // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner // split must be able to exit with an `ExceptionA` completion. this.appliesToPredecessor(pred) and - exists(FinallySplitInternal outer | + exists(FinallySplitImpl outer | outer.getNestLevel() = this.getNestLevel() - 1 and outer.exit(pred, c, inherited) and this.getType() instanceof NormalSuccessor and @@ -857,7 +856,7 @@ module FinallySplitting { } override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - succ = succ(pred, c) and + succ(pred, succ, c) and ( exit(pred, c, _) or @@ -866,19 +865,19 @@ module FinallySplitting { ) } - override Callable hasExit(ControlFlowElement pred, Completion c) { - result = succExit(pred, c) and + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { + succExit(last, scope, c) and ( - exit(pred, c, _) + exit(last, c, _) or - exit(pred, any(BreakCompletion bc), _) and + exit(last, any(BreakCompletion bc), _) and c instanceof BreakNormalCompletion ) } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and succ = any(FinallyControlFlowElement fcfe | if fcfe.isEntryNode() @@ -934,10 +933,10 @@ module ExceptionHandlerSplitting { * have two splits: one representing the `try` block throwing an `ArgumentException`, * and one representing the `try` block throwing an `ArithmeticException`. */ - class ExceptionHandlerSplitImpl extends SplitImpl, TExceptionHandlerSplit { + class ExceptionHandlerSplit extends Split, TExceptionHandlerSplit { private ExceptionClass ec; - ExceptionHandlerSplitImpl() { this = TExceptionHandlerSplit(ec) } + ExceptionHandlerSplit() { this = TExceptionHandlerSplit(ec) } /** Gets the exception type that this split represents. */ ExceptionClass getExceptionClass() { result = ec } @@ -953,18 +952,20 @@ module ExceptionHandlerSplitting { int getNextListOrder() { result = FinallySplitting::getNextListOrder() + 1 } - private class ExceptionHandlerSplitInternal extends SplitInternal, ExceptionHandlerSplitImpl { + private class ExceptionHandlerSplitImpl extends SplitImpl, ExceptionHandlerSplit { override ExceptionHandlerSplitKind getKind() { any() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { // Entry into first catch clause - exists(TryStmt ts | this.getExceptionClass() = getAThrownException(ts, pred, c) | - succ = succ(pred, c) and + exists(Statements::TryStmtTree ts | + this.getExceptionClass() = ts.getAThrownException(pred, c) + | + succ(pred, succ, c) and succ = ts.getCatchClause(0).(SpecificCatchClause) ) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } /** * Holds if this split applies to catch clause `scc`. The parameter `match` @@ -972,9 +973,9 @@ module ExceptionHandlerSplitting { * this split. */ private predicate appliesToCatchClause(SpecificCatchClause scc, TMatch match) { - exists(TryStmt ts, ExceptionClass ec | + exists(Statements::TryStmtTree ts, ExceptionClass ec | ec = this.getExceptionClass() and - ec = getAThrownException(ts, _, _) and + ec = ts.getAThrownException(_, _) and scc = ts.getACatchClause() | if scc.getCaughtExceptionType() = ec.getABaseType*() @@ -992,7 +993,7 @@ module ExceptionHandlerSplitting { */ private predicate appliesToPredecessor(ControlFlowElement pred, Completion c) { this.appliesTo(pred) and - (exists(succ(pred, c)) or exists(succExit(pred, c))) and + (succ(pred, _, c) or succExit(pred, _, c)) and ( pred instanceof SpecificCatchClause implies @@ -1023,7 +1024,7 @@ module ExceptionHandlerSplitting { private predicate hasLastExit(ControlFlowElement pred, ThrowCompletion c) { this.appliesToPredecessor(pred, c) and exists(TryStmt ts, SpecificCatchClause scc, int last | - pred = last(ts.getCatchClause(last), c) + last(ts.getCatchClause(last), pred, c) | ts.getCatchClause(last) = scc and scc.isLast() and @@ -1033,10 +1034,10 @@ module ExceptionHandlerSplitting { override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred, c) and - succ = succ(pred, c) and + succ(pred, succ, c) and ( // Exit out to `catch` clause block - succ = first(any(SpecificCatchClause scc).getBlock()) + first(any(SpecificCatchClause scc).getBlock(), succ) or // Exit out to a general `catch` clause succ instanceof GeneralCatchClause @@ -1046,19 +1047,19 @@ module ExceptionHandlerSplitting { ) } - override Callable hasExit(ControlFlowElement pred, Completion c) { + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { // Exit out from last `catch` clause (no catch clauses match) - this.hasLastExit(pred, c) and - result = succExit(pred, c) + this.hasLastExit(last, c) and + succExit(last, scope, c) } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred, c) and - succ = succ(pred, c) and - not succ = first(any(SpecificCatchClause scc).getBlock()) and + succ(pred, succ, c) and + not first(any(SpecificCatchClause scc).getBlock(), succ) and not succ instanceof GeneralCatchClause and not exists(TryStmt ts, SpecificCatchClause scc, int last | - pred = last(ts.getCatchClause(last), c) + last(ts.getCatchClause(last), pred, c) | ts.getCatchClause(last) = scc and scc.isLast() @@ -1206,11 +1207,11 @@ module BooleanSplitting { * that the condition on line 1 took the `true` branch, and one representing that * the condition on line 1 took the `false` branch. */ - class BooleanSplitImpl extends SplitImpl, TBooleanSplit { + class BooleanSplit extends Split, TBooleanSplit { private BooleanSplitSubKind kind; private boolean branch; - BooleanSplitImpl() { this = TBooleanSplit(kind, branch) } + BooleanSplit() { this = TBooleanSplit(kind, branch) } /** Gets the kind of this Boolean split. */ BooleanSplitSubKind getSubKind() { result = kind } @@ -1266,18 +1267,18 @@ module BooleanSplitting { Completion c ) { kind.startsSplit(pred) and - succ = succ(pred, c) and + succ(pred, succ, c) and b = c.getInnerCompletion().(BooleanCompletion).getValue() } - private class BooleanSplitInternal extends SplitInternal, BooleanSplitImpl { + private class BooleanSplitImpl extends SplitImpl, BooleanSplit { override BooleanSplitKind getKind() { result.getSubKind() = this.getSubKind() } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { hasEntry0(pred, succ, this.getSubKind(), this.getBranch(), c) } - override predicate hasEntry(Callable c, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } private ConditionBlock getACorrelatedCondition(boolean inverted) { this.getSubKind().correlatesConditions(_, result, inverted) @@ -1290,7 +1291,7 @@ module BooleanSplitting { private predicate appliesToBlock(PreBasicBlock bb, Completion c) { this.appliesTo(bb) and exists(ControlFlowElement last | last = bb.getLastElement() | - (exists(succ(last, c)) or exists(succExit(last, c))) and + (succ(last, _, c) or succExit(last, _, c)) and // Respect the value recorded in this split for all correlated conditions forall(boolean inverted | bb = this.getACorrelatedCondition(inverted) | c.getInnerCompletion() instanceof BooleanCompletion @@ -1304,23 +1305,23 @@ module BooleanSplitting { override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { exists(PreBasicBlock bb | this.appliesToBlock(bb, c) | pred = bb.getLastElement() and - succ = succ(pred, c) and + succ(pred, succ, c) and // Exit this split if we can no longer reach a correlated condition not this.getSubKind().canReachCorrelatedCondition(succ) ) } - override Callable hasExit(ControlFlowElement pred, Completion c) { + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { exists(PreBasicBlock bb | this.appliesToBlock(bb, c) | - pred = bb.getLastElement() and - result = succExit(pred, c) + last = bb.getLastElement() and + succExit(last, scope, c) ) } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { exists(PreBasicBlock bb, Completion c0 | this.appliesToBlock(bb, c0) | pred = bb.getAnElement() and - succ = succ(pred, c) and + succ(pred, succ, c) and ( pred = bb.getLastElement() implies @@ -1403,13 +1404,13 @@ module LoopSplitting { } override predicate start(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - pred = last(this.getIterableExpr(), c) and + last(this.getIterableExpr(), pred, c) and succ = this } override predicate stop(ControlFlowElement pred, ControlFlowElement succ, Completion c) { pred = this and - succ = succ(pred, c) + succ(pred, succ, c) } override predicate pruneLoopCondition(ControlFlowElement pred, ConditionalCompletion c) { @@ -1437,10 +1438,10 @@ module LoopSplitting { * the `foreach` loop is guaranteed to be executed at least once, as a result of the * `args.Length == 0` check. */ - class LoopSplitImpl extends SplitImpl, TLoopSplit { + class LoopSplit extends Split, TLoopSplit { AnalyzableLoopStmt loop; - LoopSplitImpl() { this = TLoopSplit(loop) } + LoopSplit() { this = TLoopSplit(loop) } override string toString() { if loop.isUnroll() @@ -1449,12 +1450,17 @@ module LoopSplitting { } } + pragma[noinline] + private Callable enclosingCallable(AnalyzableLoopStmt loop) { + result = loop.getEnclosingCallable() + } + private int getListOrder(AnalyzableLoopStmt loop) { - exists(Callable c, int r | c = loop.getEnclosingCallable() | + exists(Callable c, int r | c = enclosingCallable(loop) | result = r + BooleanSplitting::getNextListOrder() - 1 and loop = rank[r](AnalyzableLoopStmt loop0 | - loop0.getEnclosingCallable() = c + enclosingCallable(loop0) = c | loop0 order by loop0.getLocation().getStartLine(), loop0.getLocation().getStartColumn() ) @@ -1475,14 +1481,14 @@ module LoopSplitting { override string toString() { result = "Unroll" } } - private class LoopUnrollingSplitInternal extends SplitInternal, LoopSplitImpl { + private class LoopUnrollingSplitImpl extends SplitImpl, LoopSplit { override LoopSplitKind getKind() { result = TLoopSplitKind(loop) } override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { loop.start(pred, succ, c) } - override predicate hasEntry(Callable pred, ControlFlowElement succ) { none() } + override predicate hasEntryScope(Callable scope, ControlFlowElement first) { none() } /** * Holds if this split applies to control flow element `pred`, where `pred` @@ -1490,7 +1496,7 @@ module LoopSplitting { */ private predicate appliesToPredecessor(ControlFlowElement pred, Completion c) { this.appliesTo(pred) and - (exists(succ(pred, c)) or exists(succExit(pred, c))) and + (succ(pred, _, c) or succExit(pred, _, c)) and not loop.pruneLoopCondition(pred, c) } @@ -1499,14 +1505,14 @@ module LoopSplitting { loop.stop(pred, succ, c) } - override Callable hasExit(ControlFlowElement pred, Completion c) { - this.appliesToPredecessor(pred, c) and - result = succExit(pred, c) + override predicate hasExitScope(ControlFlowElement last, Callable scope, Completion c) { + this.appliesToPredecessor(last, c) and + succExit(last, scope, c) } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred, c) and - succ = succ(pred, c) and + succ(pred, succ, c) and not loop.stop(pred, succ, c) } } @@ -1521,8 +1527,8 @@ class Splits extends TSplits { string toString() { result = splitsToString(this) } /** Gets a split belonging to this set of splits. */ - SplitInternal getASplit() { - exists(SplitInternal head, Splits tail | this = TSplitsCons(head, tail) | + SplitImpl getASplit() { + exists(SplitImpl head, Splits tail | this = TSplitsCons(head, tail) | result = head or result = tail.getASplit() @@ -1534,19 +1540,19 @@ private predicate succEntrySplitsFromRank( @top_level_exprorstmt_parent pred, ControlFlowElement succ, Splits splits, int rnk ) { splits = TSplitsNil() and - succ = succEntry(pred) and + succEntry(pred, succ) and rnk = 0 or - exists(SplitInternal head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) | + exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) | splits = TSplitsCons(head, tail) ) } private predicate succEntrySplitsCons( - Callable pred, ControlFlowElement succ, SplitInternal head, Splits tail, int rnk + Callable pred, ControlFlowElement succ, SplitImpl head, Splits tail, int rnk ) { succEntrySplitsFromRank(pred, succ, tail, rnk - 1) and - head.hasEntry(pred, succ) and + head.hasEntryScope(pred, succ) and rnk = head.getKind().getListRank(succ) } @@ -1559,7 +1565,7 @@ predicate succEntrySplits( @top_level_exprorstmt_parent pred, ControlFlowElement succ, Splits succSplits, SuccessorType t ) { exists(int rnk | - succ = succEntry(pred) and + succEntry(pred, succ) and t instanceof NormalSuccessor and succEntrySplitsFromRank(pred, succ, succSplits, rnk) and // Attribute arguments in assemblies are represented as expressions, even though @@ -1568,9 +1574,9 @@ predicate succEntrySplits( succ.fromSource() | rnk = 0 and - not any(SplitInternal split).hasEntry(pred, succ) + not any(SplitImpl split).hasEntryScope(pred, succ) or - rnk = max(SplitInternal split | split.hasEntry(pred, succ) | split.getKind().getListRank(succ)) + rnk = max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ)) ) } @@ -1582,9 +1588,9 @@ predicate succExitSplits(ControlFlowElement pred, Splits predSplits, Callable su exists(Reachability::SameSplitsBlock b, Completion c | pred = b.getAnElement() | b.isReachable(predSplits) and t.matchesCompletion(c) and - succ = succExit(pred, c) and - forall(SplitInternal predSplit | predSplit = predSplits.getASplit() | - succ = predSplit.hasExit(pred, c) + succExit(pred, succ, c) and + forall(SplitImpl predSplit | predSplit = predSplits.getASplit() | + predSplit.hasExitScope(pred, succ, c) ) ) } @@ -1638,7 +1644,7 @@ private module SuccSplits { ) { pred = b.getAnElement() and b.isReachable(predSplits) and - succ = succ(pred, c) + succ(pred, succ, c) } private predicate case1b0( @@ -1650,7 +1656,7 @@ private module SuccSplits { | (succ = b.getAnElement() implies succ = b) and // Invariant 4 - not exists(SplitInternal split | split.hasEntry(pred, succ, c)) + not exists(SplitImpl split | split.hasEntry(pred, succ, c)) ) } @@ -1670,7 +1676,7 @@ private module SuccSplits { case1b0(pred, predSplits, succ, c) and except = predSplits or - exists(SplitInternal split | + exists(SplitImpl split | case1bForallCons(pred, predSplits, succ, c, split, except) and split.hasSuccessor(pred, succ, c) ) @@ -1679,7 +1685,7 @@ private module SuccSplits { pragma[noinline] private predicate case1bForallCons( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, - SplitInternal exceptHead, Splits exceptTail + SplitImpl exceptHead, Splits exceptTail ) { case1bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail)) } @@ -1698,7 +1704,7 @@ private module SuccSplits { } pragma[noinline] - private SplitInternal succInvariant1GetASplit( + private SplitImpl succInvariant1GetASplit( Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c ) { @@ -1715,7 +1721,7 @@ private module SuccSplits { | succInvariant1GetASplit(b, pred, predSplits, succ, c).hasExit(pred, succ, c) or - any(SplitInternal split).hasEntry(pred, succ, c) + any(SplitImpl split).hasEntry(pred, succ, c) ) } @@ -1734,7 +1740,7 @@ private module SuccSplits { sk.appliesTo(succ) and except = predSplits or - exists(Splits mid, SplitInternal split | + exists(Splits mid, SplitImpl split | case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, mid) and mid = TSplitsCons(split, except) | @@ -1746,8 +1752,7 @@ private module SuccSplits { pragma[nomagic] private predicate entryOfKind( - ControlFlowElement pred, ControlFlowElement succ, Completion c, SplitInternal split, - SplitKind sk + ControlFlowElement pred, ControlFlowElement succ, Completion c, SplitImpl split, SplitKind sk ) { split.hasEntry(pred, succ, c) and sk = split.getKind() @@ -1775,7 +1780,7 @@ private module SuccSplits { } pragma[nomagic] - private SplitInternal case2auxGetAPredecessorSplit( + private SplitImpl case2auxGetAPredecessorSplit( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c ) { case2aux(pred, predSplits, succ, c) and @@ -1784,7 +1789,7 @@ private module SuccSplits { /** Gets a split that should be in `succSplits`. */ pragma[nomagic] - private SplitInternal case2aSome( + private SplitImpl case2aSome( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk ) { ( @@ -1804,7 +1809,7 @@ private module SuccSplits { /** Gets a split that should be in `succSplits` at rank `rnk`. */ pragma[nomagic] - SplitInternal case2aSomeAtRank( + SplitImpl case2aSomeAtRank( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk ) { exists(SplitKind sk | result = case2aSome(pred, predSplits, succ, c, sk) | @@ -1838,15 +1843,13 @@ private module SuccSplits { case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and case2aNoneAtRank(pred, predSplits, succ, c, rnk) or - exists(Splits mid, SplitInternal split | - split = case2aCons(pred, predSplits, succ, mid, c, rnk) - | + exists(Splits mid, SplitImpl split | split = case2aCons(pred, predSplits, succ, mid, c, rnk) | succSplits = TSplitsCons(split, mid) ) } pragma[noinline] - private SplitInternal case2aCons( + private SplitImpl case2aCons( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c, int rnk ) { @@ -1873,7 +1876,7 @@ private module SuccSplits { not any(SplitKind sk).appliesTo(succ) and except = predSplits or - exists(SplitInternal split | case2bForallCons(pred, predSplits, succ, c, split, except) | + exists(SplitImpl split | case2bForallCons(pred, predSplits, succ, c, split, except) | // Invariants 2 and 3 split.hasExit(pred, succ, c) ) @@ -1882,7 +1885,7 @@ private module SuccSplits { pragma[noinline] private predicate case2bForallCons( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, - SplitInternal exceptHead, Splits exceptTail + SplitImpl exceptHead, Splits exceptTail ) { case2bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail)) } @@ -1922,22 +1925,22 @@ module Reachability { * That is, `cfe` starts a new block of elements with the same set of splits. */ private predicate startsSplits(ControlFlowElement cfe) { - cfe = succEntry(_) + succEntry(_, cfe) or - exists(SplitInternal s | + exists(SplitImpl s | s.hasEntry(_, cfe, _) or s.hasExit(_, cfe, _) ) or - exists(ControlFlowElement pred, SplitInternal split, Completion c | cfe = succ(pred, c) | + exists(ControlFlowElement pred, SplitImpl split, Completion c | succ(pred, cfe, c) | split.appliesTo(pred) and not split.hasSuccessor(pred, cfe, c) ) } private predicate intraSplitsSucc(ControlFlowElement pred, ControlFlowElement succ) { - succ = succ(pred, _) and + succ(pred, succ, _) and not startsSplits(succ) } @@ -1958,6 +1961,7 @@ module Reachability { result = this } + pragma[noinline] private SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) { exists(ControlFlowElement pred | pred = this.getAnElement() | succSplits(pred, predSplits, result, succSplits, _) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll index bd86de5d28b..88262a62e5e 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll @@ -86,7 +86,7 @@ module SuccessorTypes { override string toString() { result = getValue().toString() } override predicate matchesCompletion(Completion c) { - c.(BooleanCompletion).getInnerCompletion().(BooleanCompletion).getValue() = this.getValue() + c.(BooleanCompletion).getValue() = this.getValue() } } diff --git a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected index e6638e0fafa..b01dec6329f 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected @@ -463,15 +463,21 @@ | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | 1 | | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | 1 | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:21:159:45 | throw ...; | 1 | -| Finally.cs:159:41:159:43 | "1" | Finally.cs:159:27:159:44 | object creation of type Exception | 2 | -| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | 2 | -| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | 2 | +| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | 1 | +| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | 1 | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:27:159:44 | object creation of type Exception | 1 | +| Finally.cs:159:41:159:43 | "1" | Finally.cs:159:41:159:43 | "1" | 1 | +| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | 1 | +| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | 1 | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | 6 | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | 6 | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | 6 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | 6 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | 6 | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | 6 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | 6 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | 6 | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | 6 | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:163:17:163:42 | [finally: exception(ArgumentNullException)] call to method WriteLine | 6 | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:163:17:163:42 | [finally: exception(Exception)] call to method WriteLine | 6 | | Finally.cs:162:13:164:13 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | 6 | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Common.qll b/csharp/ql/test/library-tests/controlflow/graph/Common.qll index ee296a7c098..5945d2003b2 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Common.qll +++ b/csharp/ql/test/library-tests/controlflow/graph/Common.qll @@ -5,7 +5,9 @@ class StubFile extends File { } class SourceControlFlowElement extends ControlFlowElement { - SourceControlFlowElement() { not this.getLocation().getFile() instanceof StubFile } + SourceControlFlowElement() { + this.fromSource() and not this.getLocation().getFile() instanceof StubFile + } } class SourceControlFlowNode extends ControlFlow::Node { diff --git a/csharp/ql/test/library-tests/controlflow/graph/Condition.expected b/csharp/ql/test/library-tests/controlflow/graph/Condition.expected index 4b02ed1b9ef..8466511b6ae 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Condition.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Condition.expected @@ -612,15 +612,21 @@ conditionBlock | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | throw ...; | false | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | true | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | true | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | object creation of type Exception | false | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | "1" | false | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | false | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | false | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | false | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | true | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | true | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | {...} | false | @@ -628,11 +634,17 @@ conditionBlock | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | true | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:165:13:168:13 | catch {...} | false | | Finally.cs:158:36:158:36 | 1 | Finally.cs:159:21:159:45 | throw ...; | true | +| Finally.cs:158:36:158:36 | 1 | Finally.cs:159:27:159:44 | object creation of type Exception | true | | Finally.cs:158:36:158:36 | 1 | Finally.cs:159:41:159:43 | "1" | true | +| Finally.cs:158:36:158:36 | 1 | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | true | | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | true | +| Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | true | | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | true | +| Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | true | | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | true | +| Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | true | | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | true | +| Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | true | | Finally.cs:176:10:176:11 | enter M9 | Finally.cs:176:10:176:11 | exit M9 (normal) | false | | Finally.cs:176:10:176:11 | enter M9 | Finally.cs:180:21:180:43 | [b1 (line 176): true] throw ...; | true | | Finally.cs:176:10:176:11 | enter M9 | Finally.cs:180:27:180:42 | [b1 (line 176): true] object creation of type ExceptionA | true | @@ -1483,14 +1495,20 @@ conditionFlow | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | Finally.cs:165:13:168:13 | catch {...} | false | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | {...} | true | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | catch {...} | false | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | {...} | true | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | catch {...} | false | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | true | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | false | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | true | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | false | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | true | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | false | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | true | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | false | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | true | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | false | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | true | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | false | | Finally.cs:180:17:180:18 | access to parameter b1 | Finally.cs:180:27:180:42 | [b1 (line 176): true] object creation of type ExceptionA | true | | Finally.cs:180:17:180:18 | access to parameter b1 | Finally.cs:183:9:192:9 | [b1 (line 176): false] {...} | false | | Finally.cs:186:21:186:22 | [b1 (line 176): false] access to parameter b2 | Finally.cs:176:10:176:11 | exit M9 (normal) | false | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql b/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql index 6ca83c44889..71529e8a1f2 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/Consistency.ql @@ -58,7 +58,7 @@ query predicate nonUniqueSetRepresentation(Splits s1, Splits s2) { query predicate breakInvariant2( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, - SplitInternal split, Completion c + SplitImpl split, Completion c ) { succSplits(pred, predSplits, succ, succSplits, c) and split = predSplits.getASplit() and @@ -68,7 +68,7 @@ query predicate breakInvariant2( query predicate breakInvariant3( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, - SplitInternal split, Completion c + SplitImpl split, Completion c ) { succSplits(pred, predSplits, succ, succSplits, c) and split = predSplits.getASplit() and @@ -79,7 +79,7 @@ query predicate breakInvariant3( query predicate breakInvariant4( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, - SplitInternal split, Completion c + SplitImpl split, Completion c ) { succSplits(pred, predSplits, succ, succSplits, c) and split.hasEntry(pred, succ, c) and @@ -89,7 +89,7 @@ query predicate breakInvariant4( query predicate breakInvariant5( ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, - SplitInternal split, Completion c + SplitImpl split, Completion c ) { succSplits(pred, predSplits, succ, succSplits, c) and split = succSplits.getASplit() and diff --git a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected index 3ddc33dbf00..4f26216bf74 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected @@ -2082,38 +2082,56 @@ dominance | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | | Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:21:159:45 | throw ...; | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:27:159:44 | object creation of type Exception | +| Finally.cs:159:41:159:43 | "1" | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | +| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | +| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | +| Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | +| Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | +| Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | +| Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | +| Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | +| Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [exception: Exception] "1" | | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | | Finally.cs:161:52:161:54 | [exception: Exception] "1" | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | +| Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | +| Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | +| Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:163:17:163:43 | [finally: exception(ArgumentNullException)] ...; | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:163:17:163:43 | [finally: exception(Exception)] ...; | | Finally.cs:162:13:164:13 | {...} | Finally.cs:163:17:163:43 | ...; | @@ -6040,41 +6058,53 @@ postDominance | Finally.cs:158:21:158:36 | ... == ... | Finally.cs:158:36:158:36 | 1 | | Finally.cs:158:21:158:36 | [finally: exception(ArgumentNullException)] ... == ... | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | | Finally.cs:158:21:158:36 | [finally: exception(Exception)] ... == ... | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | -| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | -| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | -| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:41:159:43 | "1" | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:159:21:159:45 | throw ...; | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | +| Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | +| Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | +| Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | +| Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | +| Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | +| Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | Finally.cs:161:52:161:54 | [exception: Exception] "1" | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | | Finally.cs:161:52:161:54 | [exception: Exception] "1" | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | +| Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | +| Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | +| Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | | Finally.cs:163:17:163:42 | [finally: exception(ArgumentNullException)] call to method WriteLine | Finally.cs:163:35:163:41 | [finally: exception(ArgumentNullException)] access to array element | | Finally.cs:163:17:163:42 | [finally: exception(Exception)] call to method WriteLine | Finally.cs:163:35:163:41 | [finally: exception(Exception)] access to array element | | Finally.cs:163:17:163:42 | call to method WriteLine | Finally.cs:163:35:163:41 | access to array element | @@ -9348,15 +9378,21 @@ blockDominance | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | "1" | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:147:10:147:11 | enter M8 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | | Finally.cs:147:10:147:11 | enter M8 | Finally.cs:162:13:164:13 | {...} | @@ -9369,9 +9405,11 @@ blockDominance | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:152:17:152:50 | throw ...; | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | +| Finally.cs:152:17:152:50 | throw ...; | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:152:17:152:50 | throw ...; | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:147:10:147:11 | exit M8 (abnormal) | @@ -9382,12 +9420,16 @@ blockDominance | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | +| Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | +| Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | @@ -9395,44 +9437,69 @@ blockDominance | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | +| Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | | Finally.cs:155:9:169:9 | [finally: exception(Exception)] {...} | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | | Finally.cs:155:9:169:9 | {...} | Finally.cs:147:10:147:11 | exit M8 (normal) | | Finally.cs:155:9:169:9 | {...} | Finally.cs:155:9:169:9 | {...} | | Finally.cs:155:9:169:9 | {...} | Finally.cs:158:36:158:36 | 1 | | Finally.cs:155:9:169:9 | {...} | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:155:9:169:9 | {...} | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:155:9:169:9 | {...} | Finally.cs:159:41:159:43 | "1" | | Finally.cs:155:9:169:9 | {...} | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:155:9:169:9 | {...} | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:155:9:169:9 | {...} | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:155:9:169:9 | {...} | Finally.cs:162:13:164:13 | {...} | | Finally.cs:155:9:169:9 | {...} | Finally.cs:165:13:168:13 | catch {...} | | Finally.cs:158:36:158:36 | 1 | Finally.cs:158:36:158:36 | 1 | | Finally.cs:158:36:158:36 | 1 | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:158:36:158:36 | 1 | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:158:36:158:36 | 1 | Finally.cs:159:41:159:43 | "1" | +| Finally.cs:158:36:158:36 | 1 | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | +| Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | | Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | +| Finally.cs:158:36:158:36 | [finally: exception(ArgumentNullException)] 1 | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | +| Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | | Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | +| Finally.cs:158:36:158:36 | [finally: exception(Exception)] 1 | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | +| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | +| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | +| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:159:41:159:43 | "1" | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:41:159:43 | "1" | +| Finally.cs:159:41:159:43 | "1" | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | +| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | +| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | +| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | +| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | | Finally.cs:162:13:164:13 | {...} | Finally.cs:162:13:164:13 | {...} | @@ -11949,9 +12016,11 @@ postBlockDominance | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:155:9:169:9 | {...} | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:158:36:158:36 | 1 | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:159:41:159:43 | "1" | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:162:13:164:13 | {...} | | Finally.cs:147:10:147:11 | exit M8 (normal) | Finally.cs:165:13:168:13 | catch {...} | | Finally.cs:152:17:152:50 | throw ...; | Finally.cs:152:17:152:50 | throw ...; | @@ -11965,17 +12034,23 @@ postBlockDominance | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:21:159:45 | throw ...; | +| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | +| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:41:159:43 | "1" | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:159:21:159:45 | throw ...; | -| Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:159:41:159:43 | "1" | +| Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:159:27:159:44 | object creation of type Exception | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | | Finally.cs:162:13:164:13 | {...} | Finally.cs:162:13:164:13 | {...} | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected b/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected index eec45eedf43..f720180b0f4 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected @@ -2251,40 +2251,58 @@ nodeEnclosing | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [exception: Exception] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | {...} | Finally.cs:147:10:147:11 | M8 | @@ -5068,15 +5086,21 @@ blockEnclosing | Finally.cs:159:21:159:45 | [finally: exception(ArgumentNullException)] throw ...; | Finally.cs:147:10:147:11 | M8 | | Finally.cs:159:21:159:45 | [finally: exception(Exception)] throw ...; | Finally.cs:147:10:147:11 | M8 | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:147:10:147:11 | M8 | | Finally.cs:159:41:159:43 | "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:147:10:147:11 | M8 | | Finally.cs:162:13:164:13 | {...} | Finally.cs:147:10:147:11 | M8 | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql index 3c3abea6907..08f37467efe 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql @@ -2,6 +2,6 @@ import csharp import Common import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl -from SourceControlFlowElement cfe -where cfe.fromSource() -select cfe, first(cfe) +from SourceControlFlowElement cfe, ControlFlowElement first +where first(cfe, first) +select cfe, first diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected index e43bcb9651e..9d03fe5bbc0 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected @@ -2034,11 +2034,13 @@ | Finally.cs:157:13:160:13 | {...} | Finally.cs:158:21:158:36 | ... == ... | false | | Finally.cs:157:13:160:13 | {...} | Finally.cs:159:21:159:45 | throw ...; | throw(Exception) | | Finally.cs:157:13:160:13 | {...} | Finally.cs:159:27:159:44 | object creation of type Exception | throw(Exception) | +| Finally.cs:157:13:160:13 | {...} | Finally.cs:159:41:159:43 | "1" | throw(OutOfMemoryException) | | Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:158:21:158:31 | access to property Length | throw(Exception) | | Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:158:21:158:31 | access to property Length | throw(NullReferenceException) | | Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:158:21:158:36 | ... == ... | false | | Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:159:21:159:45 | throw ...; | throw(Exception) | | Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:159:27:159:44 | object creation of type Exception | throw(Exception) | +| Finally.cs:158:17:159:45 | if (...) ... | Finally.cs:159:41:159:43 | "1" | throw(OutOfMemoryException) | | Finally.cs:158:21:158:24 | access to parameter args | Finally.cs:158:21:158:24 | access to parameter args | normal | | Finally.cs:158:21:158:31 | access to property Length | Finally.cs:158:21:158:31 | access to property Length | normal | | Finally.cs:158:21:158:31 | access to property Length | Finally.cs:158:21:158:31 | access to property Length | throw(Exception) | @@ -2050,8 +2052,10 @@ | Finally.cs:158:36:158:36 | 1 | Finally.cs:158:36:158:36 | 1 | normal | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:21:159:45 | throw ...; | throw(Exception) | | Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:27:159:44 | object creation of type Exception | throw(Exception) | +| Finally.cs:159:21:159:45 | throw ...; | Finally.cs:159:41:159:43 | "1" | throw(OutOfMemoryException) | | Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:27:159:44 | object creation of type Exception | normal | | Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:27:159:44 | object creation of type Exception | throw(Exception) | +| Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:41:159:43 | "1" | throw(OutOfMemoryException) | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:41:159:43 | "1" | normal | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:41:159:43 | "1" | throw(OutOfMemoryException) | | Finally.cs:161:13:164:13 | catch (...) {...} | Finally.cs:161:13:164:13 | catch (...) {...} | no-match | @@ -3316,6 +3320,7 @@ | Switch.cs:156:36:156:38 | "a" | Switch.cs:156:36:156:38 | "a" | normal | | Switch.cs:156:41:156:45 | false | Switch.cs:156:41:156:45 | false | match | | Switch.cs:156:41:156:45 | false | Switch.cs:156:41:156:45 | false | no-match | +| Switch.cs:156:41:156:52 | ... => ... | Switch.cs:156:41:156:45 | false | throw(InvalidOperationException) [no-match] | | Switch.cs:156:41:156:52 | ... => ... | Switch.cs:156:41:156:52 | ... => ... | normal | | Switch.cs:156:50:156:52 | "b" | Switch.cs:156:50:156:52 | "b" | normal | | Switch.cs:157:9:160:49 | if (...) ... | Switch.cs:158:13:158:48 | call to method WriteLine | normal | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql index 5a467171176..d51cbcb82c4 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql @@ -3,6 +3,6 @@ import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl private import semmle.code.csharp.controlflow.internal.Completion import Common -from SourceControlFlowElement cfe, Completion c -where cfe.fromSource() -select cfe, last(cfe, c), c +from SourceControlFlowElement cfe, ControlFlowElement last, Completion c +where last(cfe, last, c) +select cfe, last, c diff --git a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected index 5e0711db2dc..b26ac444287 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected @@ -2327,50 +2327,74 @@ | Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:159:21:159:45 | throw ...; | semmle.label | successor | | Finally.cs:159:27:159:44 | object creation of type Exception | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | semmle.label | exception(Exception) | | Finally.cs:159:41:159:43 | "1" | Finally.cs:159:27:159:44 | object creation of type Exception | semmle.label | successor | +| Finally.cs:159:41:159:43 | "1" | Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | semmle.label | exception(OutOfMemoryException) | | Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:159:27:159:44 | [finally: exception(ArgumentNullException)] object creation of type Exception | semmle.label | successor | +| Finally.cs:159:41:159:43 | [finally: exception(ArgumentNullException)] "1" | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | semmle.label | exception(OutOfMemoryException) | | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:159:27:159:44 | [finally: exception(Exception)] object creation of type Exception | semmle.label | successor | +| Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | semmle.label | exception(OutOfMemoryException) | | Finally.cs:161:13:164:13 | [exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | semmle.label | match | | Finally.cs:161:13:164:13 | [exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | semmle.label | match | +| Finally.cs:161:13:164:13 | [exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | semmle.label | match | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | semmle.label | match | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | semmle.label | match | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | semmle.label | match | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | semmle.label | match | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | semmle.label | match | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | semmle.label | match | | Finally.cs:161:30:161:30 | [exception: Exception] Exception e | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | semmle.label | successor | | Finally.cs:161:30:161:30 | [exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | semmle.label | successor | +| Finally.cs:161:30:161:30 | [exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | semmle.label | successor | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | semmle.label | successor | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | semmle.label | successor | +| Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | semmle.label | successor | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | semmle.label | successor | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | semmle.label | successor | +| Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | semmle.label | successor | | Finally.cs:161:39:161:39 | [exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:39 | [exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | semmle.label | successor | +| Finally.cs:161:39:161:39 | [exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | semmle.label | successor | +| Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | semmle.label | successor | +| Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | semmle.label | successor | | Finally.cs:161:39:161:47 | [exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [exception: Exception] "1" | semmle.label | successor | | Finally.cs:161:39:161:47 | [exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | semmle.label | successor | +| Finally.cs:161:39:161:47 | [exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | semmle.label | successor | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | semmle.label | successor | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | semmle.label | successor | +| Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | semmle.label | successor | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | semmle.label | successor | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | semmle.label | successor | +| Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | semmle.label | successor | | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | Finally.cs:162:13:164:13 | {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | Finally.cs:165:13:168:13 | catch {...} | semmle.label | false | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | catch {...} | semmle.label | false | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | {...} | semmle.label | true | +| Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | catch {...} | semmle.label | false | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | semmle.label | false | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | semmle.label | false | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | semmle.label | true | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(ArgumentNullException)] catch {...} | semmle.label | false | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | semmle.label | false | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | semmle.label | true | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | semmle.label | false | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | semmle.label | true | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:165:13:168:13 | [finally: exception(Exception)] catch {...} | semmle.label | false | | Finally.cs:161:52:161:54 | [exception: Exception] "1" | Finally.cs:161:39:161:54 | [exception: Exception] ... == ... | semmle.label | successor | | Finally.cs:161:52:161:54 | [exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [exception: NullReferenceException] ... == ... | semmle.label | successor | +| Finally.cs:161:52:161:54 | [exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [exception: OutOfMemoryException] ... == ... | semmle.label | successor | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | semmle.label | successor | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | semmle.label | successor | +| Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | semmle.label | successor | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | semmle.label | successor | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | semmle.label | successor | +| Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | semmle.label | successor | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:163:17:163:43 | [finally: exception(ArgumentNullException)] ...; | semmle.label | successor | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:163:17:163:43 | [finally: exception(Exception)] ...; | semmle.label | successor | | Finally.cs:162:13:164:13 | {...} | Finally.cs:163:17:163:43 | ...; | semmle.label | successor | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected b/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected index 6a034dc1390..2dbbf608e5a 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected @@ -833,28 +833,40 @@ finallyNode | Finally.cs:159:41:159:43 | [finally: exception(Exception)] "1" | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: Exception] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: NullReferenceException] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:13:164:13 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: Exception] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: NullReferenceException] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:13:164:13 | [finally: exception(Exception), exception: OutOfMemoryException] catch (...) {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: Exception] Exception e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: NullReferenceException] Exception e | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:30:161:30 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] Exception e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: Exception] Exception e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: NullReferenceException] Exception e | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:30:161:30 | [finally: exception(Exception), exception: OutOfMemoryException] Exception e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: Exception] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:39 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: Exception] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: NullReferenceException] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:39 | [finally: exception(Exception), exception: OutOfMemoryException] access to local variable e | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: Exception] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: NullReferenceException] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:47 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: Exception] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: NullReferenceException] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:47 | [finally: exception(Exception), exception: OutOfMemoryException] access to property Message | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: Exception] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: Exception] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: NullReferenceException] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:39:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] ... == ... | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: Exception] "1" | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: NullReferenceException] "1" | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:52:161:54 | [finally: exception(ArgumentNullException), exception: OutOfMemoryException] "1" | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: Exception] "1" | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: NullReferenceException] "1" | Finally.cs:149:9:169:9 | try {...} ... | +| Finally.cs:161:52:161:54 | [finally: exception(Exception), exception: OutOfMemoryException] "1" | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:162:13:164:13 | [finally: exception(ArgumentNullException)] {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:162:13:164:13 | [finally: exception(Exception)] {...} | Finally.cs:149:9:169:9 | try {...} ... | | Finally.cs:163:17:163:42 | [finally: exception(ArgumentNullException)] call to method WriteLine | Finally.cs:149:9:169:9 | try {...} ... | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql b/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql index b6813cbcc8a..9ffd6f48cba 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/Nodes.ql @@ -2,21 +2,23 @@ import csharp import ControlFlow import Common import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl -import semmle.code.csharp.controlflow.internal.Splitting +import semmle.code.csharp.controlflow.internal.Splitting as Splitting import Nodes query predicate booleanNode(ElementNode e, BooleanSplit split) { split = e.getASplit() } class MyFinallySplitControlFlowNode extends ElementNode { MyFinallySplitControlFlowNode() { - exists(FinallySplitting::FinallySplitType type | + exists(Splitting::FinallySplitting::FinallySplitType type | type = this.getASplit().(FinallySplit).getType() | not type instanceof SuccessorTypes::NormalSuccessor ) } - TryStmt getTryStmt() { this.getElement() = FinallySplitting::getAFinallyDescendant(result) } + TryStmt getTryStmt() { + this.getElement() = Splitting::FinallySplitting::getAFinallyDescendant(result) + } } query predicate finallyNode(MyFinallySplitControlFlowNode f, TryStmt try) { try = f.getTryStmt() } From 17df059432cd7521f87acb83ae2ecf581e4ee6c2 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 18 Nov 2020 10:23:51 +0100 Subject: [PATCH 57/97] C#: Replace `matchesCompletion()` with `getAMatchingSuccessorType()` --- .../semmle/code/csharp/controlflow/Guards.qll | 8 +-- .../controlflow/internal/Completion.qll | 71 +++++++++++++++---- .../internal/ControlFlowGraphImpl.qll | 2 +- .../controlflow/internal/PreBasicBlocks.qll | 4 +- .../csharp/controlflow/internal/Splitting.qll | 8 +-- .../controlflow/internal/SuccessorType.qll | 47 +----------- 6 files changed, 68 insertions(+), 72 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll index 3bd57b848e5..3523ffbb6bb 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll @@ -110,7 +110,7 @@ module AbstractValues { override predicate branch(ControlFlowElement cfe, ConditionalSuccessor s, Expr e) { s.(BooleanSuccessor).getValue() = this.getValue() and - exists(BooleanCompletion c | s.matchesCompletion(c) | + exists(BooleanCompletion c | s = c.getAMatchingSuccessorType() | c.isValidFor(cfe) and e = cfe ) @@ -161,7 +161,7 @@ module AbstractValues { override predicate branch(ControlFlowElement cfe, ConditionalSuccessor s, Expr e) { this = TNullValue(s.(NullnessSuccessor).getValue()) and - exists(NullnessCompletion c | s.matchesCompletion(c) | + exists(NullnessCompletion c | s = c.getAMatchingSuccessorType() | c.isValidFor(cfe) and e = cfe ) @@ -190,7 +190,7 @@ module AbstractValues { override predicate branch(ControlFlowElement cfe, ConditionalSuccessor s, Expr e) { this = TMatchValue(_, s.(MatchingSuccessor).getValue()) and - exists(MatchingCompletion c, Switch switch, Case case | s.matchesCompletion(c) | + exists(MatchingCompletion c, Switch switch, Case case | s = c.getAMatchingSuccessorType() | c.isValidFor(cfe) and switchMatching(switch, case, cfe) and e = switch.getExpr() and @@ -227,7 +227,7 @@ module AbstractValues { override predicate branch(ControlFlowElement cfe, ConditionalSuccessor s, Expr e) { this = TEmptyCollectionValue(s.(EmptinessSuccessor).getValue()) and - exists(EmptinessCompletion c, ForeachStmt fs | s.matchesCompletion(c) | + exists(EmptinessCompletion c, ForeachStmt fs | s = c.getAMatchingSuccessorType() | c.isValidFor(cfe) and foreachEmptiness(fs, cfe) and e = fs.getIterableExpr() diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll index af1f29663b7..333cfafc051 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll @@ -24,10 +24,12 @@ private import semmle.code.csharp.commons.Assertions private import semmle.code.csharp.commons.Constants private import semmle.code.csharp.frameworks.System private import NonReturning +private import SuccessorType +private import SuccessorTypes // Internal representation of completions private newtype TCompletion = - TNormalCompletion() or + TSimpleCompletion() or TBooleanCompletion(boolean b) { b = true or b = false } or TNullnessCompletion(boolean isNull) { isNull = true or isNull = false } or TMatchingCompletion(boolean isMatch) { isMatch = true or isMatch = false } or @@ -78,7 +80,7 @@ private predicate completionIsValidForStmt(Stmt s, Completion c) { /** * A completion of a statement or an expression. */ -class Completion extends TCompletion { +abstract class Completion extends TCompletion { /** * Holds if this completion is valid for control flow element `cfe`. * @@ -143,7 +145,7 @@ class Completion extends TCompletion { not mustHaveNullnessCompletion(cfe) and not mustHaveMatchingCompletion(cfe) and not mustHaveEmptinessCompletion(cfe) and - this = TNormalCompletion() + this = TSimpleCompletion() } /** @@ -167,8 +169,11 @@ class Completion extends TCompletion { */ Completion getOuterCompletion() { result = this } + /** Gets a successor type that matches this completion. */ + abstract SuccessorType getAMatchingSuccessorType(); + /** Gets a textual representation of this completion. */ - string toString() { none() } + abstract string toString(); } /** Holds if expression `e` has the Boolean constant value `value`. */ @@ -529,10 +534,10 @@ private predicate mustHaveEmptinessCompletion(ControlFlowElement cfe) { foreachE */ abstract class NormalCompletion extends Completion { } -/** - * A class to make `TNormalCompletion` a `NormalCompletion` - */ -class SimpleCompletion extends NormalCompletion, TNormalCompletion { +/** A simple (normal) completion. */ +class SimpleCompletion extends NormalCompletion, TSimpleCompletion { + override NormalSuccessor getAMatchingSuccessorType() { any() } + override string toString() { result = "normal" } } @@ -558,6 +563,8 @@ class BooleanCompletion extends ConditionalCompletion { BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } + override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value } + override string toString() { result = value.toString() } } @@ -576,11 +583,17 @@ class FalseCompletion extends BooleanCompletion { * `null` or non-`null`. */ class NullnessCompletion extends ConditionalCompletion, TNullnessCompletion { + private boolean value; + + NullnessCompletion() { this = TNullnessCompletion(value) } + /** Holds if the last sub expression of this expression evaluates to `null`. */ - predicate isNull() { this = TNullnessCompletion(true) } + predicate isNull() { value = true } /** Holds if the last sub expression of this expression evaluates to a non-`null` value. */ - predicate isNonNull() { this = TNullnessCompletion(false) } + predicate isNonNull() { value = false } + + override NullnessSuccessor getAMatchingSuccessorType() { result.getValue() = value } override string toString() { if this.isNull() then result = "null" else result = "non-null" } } @@ -590,11 +603,17 @@ class NullnessCompletion extends ConditionalCompletion, TNullnessCompletion { * `switch` statement. */ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { + private boolean value; + + MatchingCompletion() { this = TMatchingCompletion(value) } + /** Holds if there is a match. */ - predicate isMatch() { this = TMatchingCompletion(true) } + predicate isMatch() { value = true } /** Holds if there is not a match. */ - predicate isNonMatch() { this = TMatchingCompletion(false) } + predicate isNonMatch() { value = false } + + override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value } override string toString() { if this.isMatch() then result = "match" else result = "no-match" } } @@ -604,8 +623,14 @@ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { * a test in a `foreach` statement. */ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { + private boolean value; + + EmptinessCompletion() { this = TEmptinessCompletion(value) } + /** Holds if the emptiness test evaluates to `true`. */ - predicate isEmpty() { this = TEmptinessCompletion(true) } + predicate isEmpty() { value = true } + + override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value } override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } } @@ -616,7 +641,7 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { * * This completion is added for technical reasons only: when a loop * body can complete with a break completion, the loop itself completes - * normally. However, if we choose `TNormalCompletion` as the completion + * normally. However, if we choose `TSimpleCompletion` as the completion * of the loop, we lose the information that the last element actually * completed with a break, meaning that the control flow edge out of the * breaking node cannot be marked with a `break` label. @@ -634,10 +659,12 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { * The `break` on line 3 completes with a `TBreakCompletion`, therefore * the `while` loop can complete with a `TBreakNormalCompletion`, so we * get an edge `break --break--> return`. (If we instead used a - * `TNormalCompletion`, we would get a less precise edge + * `TSimpleCompletion`, we would get a less precise edge * `break --normal--> return`.) */ class BreakNormalCompletion extends NormalCompletion, TBreakNormalCompletion { + override BreakSuccessor getAMatchingSuccessorType() { any() } + override string toString() { result = "normal (break)" } } @@ -673,6 +700,8 @@ class NestedCompletion extends Completion, TNestedCompletion { override Completion getOuterCompletion() { result = outer } + override SuccessorType getAMatchingSuccessorType() { none() } + override string toString() { result = outer + " [" + inner + "]" } } @@ -686,6 +715,8 @@ class ReturnCompletion extends Completion { this = TNestedCompletion(_, TReturnCompletion()) } + override ReturnSuccessor getAMatchingSuccessorType() { any() } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TReturnCompletion() and result = "return" @@ -703,6 +734,8 @@ class BreakCompletion extends Completion { this = TNestedCompletion(_, TBreakCompletion()) } + override BreakSuccessor getAMatchingSuccessorType() { any() } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TBreakCompletion() and result = "break" @@ -720,6 +753,8 @@ class ContinueCompletion extends Completion { this = TNestedCompletion(_, TContinueCompletion()) } + override ContinueSuccessor getAMatchingSuccessorType() { any() } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TContinueCompletion() and result = "continue" @@ -741,6 +776,8 @@ class GotoCompletion extends Completion { /** Gets the label of the `goto` completion. */ string getLabel() { result = label } + override GotoSuccessor getAMatchingSuccessorType() { result.getLabel() = label } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TGotoCompletion(label) and result = "goto(" + label + ")" @@ -762,6 +799,8 @@ class ThrowCompletion extends Completion { /** Gets the type of the exception being thrown. */ ExceptionClass getExceptionClass() { result = ec } + override ExceptionSuccessor getAMatchingSuccessorType() { result.getExceptionClass() = ec } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TThrowCompletion(ec) and result = "throw(" + ec + ")" @@ -783,6 +822,8 @@ class ExitCompletion extends Completion { this = TNestedCompletion(_, TExitCompletion()) } + override ExitSuccessor getAMatchingSuccessorType() { any() } + override string toString() { // `NestedCompletion` defines `toString()` for the other case this = TExitCompletion() and result = "exit" diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll index d076e89d6b0..96c52650a36 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -1619,7 +1619,7 @@ private module Cached { result = TElementNode(succElement, succSplits) | succSplits(predElement, predSplits, succElement, succSplits, c) and - t.matchesCompletion(c) + t = c.getAMatchingSuccessorType() ) ) or diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll index c4a144d7631..ec40d97a1ef 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/PreBasicBlocks.qll @@ -54,7 +54,7 @@ class PreBasicBlock extends ControlFlowElement { PreBasicBlock() { startsBB(this) } PreBasicBlock getASuccessorByType(SuccessorType t) { - succ(this.getLastElement(), result, any(Completion c | t.matchesCompletion(c))) + succ(this.getLastElement(), result, any(Completion c | t = c.getAMatchingSuccessorType())) } PreBasicBlock getASuccessor() { result = this.getASuccessorByType(_) } @@ -127,7 +127,7 @@ class ConditionBlock extends PreBasicBlock { predicate controls(PreBasicBlock controlled, SuccessorTypes::ConditionalSuccessor s) { exists(PreBasicBlock succ, ConditionalCompletion c | immediatelyControls(succ, c) | succ.dominates(controlled) and - s.matchesCompletion(c) + s = c.getAMatchingSuccessorType() ) } } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll index 551de92e73b..687e6b97bdf 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll @@ -635,7 +635,7 @@ module FinallySplitting { // If the entry into the `finally` block completes with any normal completion, // it simply means normal execution after the `finally` block this instanceof NormalSuccessor - else this.matchesCompletion(c) + else this = c.getAMatchingSuccessorType() } } @@ -806,7 +806,7 @@ module FinallySplitting { // does not require another completion to be recovered inherited = false and ( - type.matchesCompletion(c) + type = c.getAMatchingSuccessorType() or not c instanceof NormalCompletion or @@ -816,7 +816,7 @@ module FinallySplitting { // Finally block can exit with completion `c` inherited from try/catch // block: must match this split inherited = true and - type.matchesCompletion(c) and + type = c.getAMatchingSuccessorType() and not type instanceof NormalSuccessor ) ) @@ -1587,7 +1587,7 @@ predicate succEntrySplits( predicate succExitSplits(ControlFlowElement pred, Splits predSplits, Callable succ, SuccessorType t) { exists(Reachability::SameSplitsBlock b, Completion c | pred = b.getAnElement() | b.isReachable(predSplits) and - t.matchesCompletion(c) and + t = c.getAMatchingSuccessorType() and succExit(pred, succ, c) and forall(SplitImpl predSplit | predSplit = predSplits.getASplit() | predSplit.hasExitScope(pred, succ, c) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll index 88262a62e5e..648c2cd847c 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/SuccessorType.qll @@ -28,7 +28,7 @@ class SuccessorType extends TSuccessorType { string toString() { none() } /** Holds if this successor type matches completion `c`. */ - predicate matchesCompletion(Completion c) { none() } + deprecated predicate matchesCompletion(Completion c) { this = c.getAMatchingSuccessorType() } } /** Provides different types of control flow successor types. */ @@ -36,12 +36,6 @@ module SuccessorTypes { /** A normal control flow successor. */ class NormalSuccessor extends SuccessorType, TSuccessorSuccessor { override string toString() { result = "successor" } - - override predicate matchesCompletion(Completion c) { - c instanceof NormalCompletion and - not c instanceof ConditionalCompletion and - not c instanceof BreakNormalCompletion - } } /** @@ -84,10 +78,6 @@ module SuccessorTypes { override boolean getValue() { this = TBooleanSuccessor(result) } override string toString() { result = getValue().toString() } - - override predicate matchesCompletion(Completion c) { - c.(BooleanCompletion).getValue() = this.getValue() - } } /** @@ -123,10 +113,6 @@ module SuccessorTypes { override boolean getValue() { this = TNullnessSuccessor(result) } override string toString() { if this.isNull() then result = "null" else result = "non-null" } - - override predicate matchesCompletion(Completion c) { - if this.isNull() then c.(NullnessCompletion).isNull() else c.(NullnessCompletion).isNonNull() - } } /** @@ -168,12 +154,6 @@ module SuccessorTypes { override boolean getValue() { this = TMatchingSuccessor(result) } override string toString() { if this.isMatch() then result = "match" else result = "no-match" } - - override predicate matchesCompletion(Completion c) { - if this.isMatch() - then c.(MatchingCompletion).isMatch() - else c.(MatchingCompletion).isNonMatch() - } } /** @@ -215,12 +195,6 @@ module SuccessorTypes { override boolean getValue() { this = TEmptinessSuccessor(result) } override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } - - override predicate matchesCompletion(Completion c) { - if this.isEmpty() - then c.(EmptinessCompletion).isEmpty() - else c = any(EmptinessCompletion ec | not ec.isEmpty()) - } } /** @@ -240,8 +214,6 @@ module SuccessorTypes { */ class ReturnSuccessor extends SuccessorType, TReturnSuccessor { override string toString() { result = "return" } - - override predicate matchesCompletion(Completion c) { c instanceof ReturnCompletion } } /** @@ -265,11 +237,6 @@ module SuccessorTypes { */ class BreakSuccessor extends SuccessorType, TBreakSuccessor { override string toString() { result = "break" } - - override predicate matchesCompletion(Completion c) { - c instanceof BreakCompletion or - c instanceof BreakNormalCompletion - } } /** @@ -293,8 +260,6 @@ module SuccessorTypes { */ class ContinueSuccessor extends SuccessorType, TContinueSuccessor { override string toString() { result = "continue" } - - override predicate matchesCompletion(Completion c) { c instanceof ContinueCompletion } } /** @@ -322,10 +287,6 @@ module SuccessorTypes { string getLabel() { this = TGotoSuccessor(result) } override string toString() { result = "goto(" + this.getLabel() + ")" } - - override predicate matchesCompletion(Completion c) { - c.(GotoCompletion).getLabel() = this.getLabel() - } } /** @@ -350,10 +311,6 @@ module SuccessorTypes { ExceptionClass getExceptionClass() { this = TExceptionSuccessor(result) } override string toString() { result = "exception(" + getExceptionClass().getName() + ")" } - - override predicate matchesCompletion(Completion c) { - c.(ThrowCompletion).getExceptionClass() = getExceptionClass() - } } /** @@ -374,7 +331,5 @@ module SuccessorTypes { */ class ExitSuccessor extends SuccessorType, TExitSuccessor { override string toString() { result = "exit" } - - override predicate matchesCompletion(Completion c) { c instanceof ExitCompletion } } } From f0f5d44b337f656f3349d154c1813ad1183c131c Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 20 Nov 2020 10:55:14 +0100 Subject: [PATCH 58/97] C#: Replace `BreakNormalCompletion` with a nested completion --- .../controlflow/internal/Completion.qll | 130 +++++---- .../internal/ControlFlowGraphImpl.qll | 32 +-- .../csharp/controlflow/internal/Splitting.qll | 6 +- .../controlflow/graph/Condition.expected | 1 + .../controlflow/graph/ExitElement.expected | 272 ++++++++---------- .../controlflow/graph/NodeGraph.expected | 2 +- 6 files changed, 215 insertions(+), 228 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll index 333cfafc051..f9d45deb011 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll @@ -36,23 +36,28 @@ private newtype TCompletion = TEmptinessCompletion(boolean isEmpty) { isEmpty = true or isEmpty = false } or TReturnCompletion() or TBreakCompletion() or - TBreakNormalCompletion() or TContinueCompletion() or TGotoCompletion(string label) { label = any(GotoStmt gs).getLabel() } or TThrowCompletion(ExceptionClass ec) or TExitCompletion() or - TNestedCompletion(ConditionalCompletion inner, Completion outer) { - outer = TReturnCompletion() + TNestedCompletion(Completion inner, Completion outer) { + inner instanceof NormalCompletion and + ( + outer = TReturnCompletion() + or + outer = TBreakCompletion() + or + outer = TContinueCompletion() + or + outer = TGotoCompletion(_) + or + outer = TThrowCompletion(_) + or + outer = TExitCompletion() + ) or - outer = TBreakCompletion() - or - outer = TContinueCompletion() - or - outer = TGotoCompletion(_) - or - outer = TThrowCompletion(_) - or - outer = TExitCompletion() + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion } pragma[noinline] @@ -534,8 +539,10 @@ private predicate mustHaveEmptinessCompletion(ControlFlowElement cfe) { foreachE */ abstract class NormalCompletion extends Completion { } +abstract private class NonNestedNormalCompletion extends NormalCompletion { } + /** A simple (normal) completion. */ -class SimpleCompletion extends NormalCompletion, TSimpleCompletion { +class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { override NormalSuccessor getAMatchingSuccessorType() { any() } override string toString() { result = "normal" } @@ -547,7 +554,7 @@ class SimpleCompletion extends NormalCompletion, TSimpleCompletion { * completion (`NullnessCompletion`), a matching completion (`MatchingCompletion`), * or an emptiness completion (`EmptinessCompletion`). */ -abstract class ConditionalCompletion extends NormalCompletion { } +abstract class ConditionalCompletion extends NonNestedNormalCompletion { } /** * A completion that represents evaluation of an expression @@ -635,39 +642,6 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } } -/** - * A completion that represents evaluation of a statement or - * expression resulting in a loop break. - * - * This completion is added for technical reasons only: when a loop - * body can complete with a break completion, the loop itself completes - * normally. However, if we choose `TSimpleCompletion` as the completion - * of the loop, we lose the information that the last element actually - * completed with a break, meaning that the control flow edge out of the - * breaking node cannot be marked with a `break` label. - * - * Example: - * - * ```csharp - * while (...) { - * ... - * break; - * } - * return; - * ``` - * - * The `break` on line 3 completes with a `TBreakCompletion`, therefore - * the `while` loop can complete with a `TBreakNormalCompletion`, so we - * get an edge `break --break--> return`. (If we instead used a - * `TSimpleCompletion`, we would get a less precise edge - * `break --normal--> return`.) - */ -class BreakNormalCompletion extends NormalCompletion, TBreakNormalCompletion { - override BreakSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { result = "normal (break)" } -} - /** * A nested completion. For example, in * @@ -691,12 +665,17 @@ class BreakNormalCompletion extends NormalCompletion, TBreakNormalCompletion { * and an inner `false` completion. `b2` also has a (normal) `true` completion. */ class NestedCompletion extends Completion, TNestedCompletion { - private ConditionalCompletion inner; - private Completion outer; + Completion inner; + Completion outer; NestedCompletion() { this = TNestedCompletion(inner, outer) } - override ConditionalCompletion getInnerCompletion() { result = inner } + /** Gets a completion that is compatible with the inner completion. */ + Completion getAnInnerCompatibleCompletion() { + result.getOuterCompletion() = this.getInnerCompletion() + } + + override Completion getInnerCompletion() { result = inner } override Completion getOuterCompletion() { result = outer } @@ -705,6 +684,57 @@ class NestedCompletion extends Completion, TNestedCompletion { override string toString() { result = outer + " [" + inner + "]" } } +/** + * A nested completion for a loop that exists with a `break`. + * + * This completion is added for technical reasons only: when a loop + * body can complete with a break completion, the loop itself completes + * normally. However, if we choose `TSimpleCompletion` as the completion + * of the loop, we lose the information that the last element actually + * completed with a break, meaning that the control flow edge out of the + * breaking node cannot be marked with a `break` label. + * + * Example: + * + * ```csharp + * while (...) { + * ... + * break; + * } + * return; + * ``` + * + * The `break` on line 3 completes with a `TBreakCompletion`, therefore + * the `while` loop can complete with a `NestedBreakCompletion`, so we + * get an edge `break --break--> return`. (If we instead used a + * `TSimpleCompletion`, we would get a less precise edge + * `break --normal--> return`.) + */ +class NestedBreakCompletion extends NormalCompletion, NestedCompletion { + NestedBreakCompletion() { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion + } + + override BreakCompletion getInnerCompletion() { result = inner } + + override SimpleCompletion getOuterCompletion() { result = outer } + + override Completion getAnInnerCompatibleCompletion() { + result = inner and + outer = TSimpleCompletion() + or + result = TNestedCompletion(outer, inner) + } + + override SuccessorType getAMatchingSuccessorType() { + outer instanceof SimpleCompletion and + result instanceof BreakSuccessor + or + result = outer.(ConditionalCompletion).getAMatchingSuccessorType() + } +} + /** * A completion that represents evaluation of a statement or an * expression resulting in a return from a callable. diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll index 96c52650a36..1a67b061425 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -1040,8 +1040,7 @@ module Statements { c instanceof NormalCompletion or // A statement exits with a `break` completion - last(this.getStmt(_), last, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + last(this.getStmt(_), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) or // A statement exits abnormally last(this.getStmt(_), last, c) and @@ -1123,12 +1122,8 @@ module Statements { last(this.getCondition(), last, c) and c instanceof FalseCompletion or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - last(body, last, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + // Body exits with a break completion + last(body, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) or // Body exits with a completion that does not continue the loop last(body, last, c) and @@ -1252,12 +1247,8 @@ module Statements { last = this and c.(EmptinessCompletion).isEmpty() or - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - last(this.getBody(), last, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + // Body exits with a break completion + last(this.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) or // Body exits abnormally last(this.getBody(), last, c) and @@ -1375,15 +1366,10 @@ module Statements { or // If the `finally` block completes normally, it inherits any non-normal // completion that was current before the `finally` block was entered - exists(NormalCompletion finally, Completion outer | this.lastFinally(last, finally, outer) | - c = - any(NestedCompletion nc | - nc.getInnerCompletion() = finally and nc.getOuterCompletion() = outer - ) - or - not finally instanceof ConditionalCompletion and - c = outer - ) + c = + any(NestedCompletion nc | + this.lastFinally(last, nc.getAnInnerCompatibleCompletion(), nc.getOuterCompletion()) + ) } /** diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll index 687e6b97bdf..b234a9dd43c 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll @@ -860,8 +860,7 @@ module FinallySplitting { ( exit(pred, c, _) or - exit(pred, any(BreakCompletion bc), _) and - c instanceof BreakNormalCompletion + exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) ) } @@ -870,8 +869,7 @@ module FinallySplitting { ( exit(last, c, _) or - exit(last, any(BreakCompletion bc), _) and - c instanceof BreakNormalCompletion + exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) ) } diff --git a/csharp/ql/test/library-tests/controlflow/graph/Condition.expected b/csharp/ql/test/library-tests/controlflow/graph/Condition.expected index 8466511b6ae..ded2bfbe3c5 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Condition.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Condition.expected @@ -1307,6 +1307,7 @@ conditionFlow | BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | false | | BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:32:21:32:21 | ; | true | | BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:32:21:32:21 | [finally: break] ; | true | +| BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:35:7:35:7 | ; | false | | BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:43:17:43:23 | return ...; | true | | BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:46:9:52:9 | {...} | false | | BreakInTry.cs:49:21:49:31 | ... == ... | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | false | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected index 9d03fe5bbc0..63de557d245 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected @@ -843,17 +843,13 @@ | Assignments.cs:19:9:19:17 | return ...; | Assignments.cs:19:9:19:17 | return ...; | return | | Assignments.cs:19:16:19:16 | access to parameter x | Assignments.cs:19:16:19:16 | access to parameter x | normal | | BreakInTry.cs:4:5:18:5 | {...} | BreakInTry.cs:15:17:15:28 | ... == ... | false | -| BreakInTry.cs:4:5:18:5 | {...} | BreakInTry.cs:16:17:16:17 | ; | empty | | BreakInTry.cs:4:5:18:5 | {...} | BreakInTry.cs:16:17:16:17 | ; | normal | -| BreakInTry.cs:4:5:18:5 | {...} | BreakInTry.cs:16:17:16:17 | ; | normal (break) | | BreakInTry.cs:5:9:17:9 | try {...} ... | BreakInTry.cs:15:17:15:28 | ... == ... | false | -| BreakInTry.cs:5:9:17:9 | try {...} ... | BreakInTry.cs:16:17:16:17 | ; | empty | | BreakInTry.cs:5:9:17:9 | try {...} ... | BreakInTry.cs:16:17:16:17 | ; | normal | -| BreakInTry.cs:5:9:17:9 | try {...} ... | BreakInTry.cs:16:17:16:17 | ; | normal (break) | | BreakInTry.cs:6:9:12:9 | {...} | BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:6:9:12:9 | {...} | BreakInTry.cs:10:21:10:26 | break; | normal (break) | +| BreakInTry.cs:6:9:12:9 | {...} | BreakInTry.cs:10:21:10:26 | break; | normal [break] | | BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:10:21:10:26 | break; | normal (break) | +| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:10:21:10:26 | break; | normal [break] | | BreakInTry.cs:7:26:7:28 | String arg | BreakInTry.cs:7:26:7:28 | String arg | normal | | BreakInTry.cs:7:33:7:36 | access to parameter args | BreakInTry.cs:7:33:7:36 | access to parameter args | normal | | BreakInTry.cs:8:13:11:13 | {...} | BreakInTry.cs:9:21:9:31 | ... == ... | false | @@ -876,19 +872,17 @@ | BreakInTry.cs:16:17:16:17 | ; | BreakInTry.cs:16:17:16:17 | ; | normal | | BreakInTry.cs:21:5:36:5 | {...} | BreakInTry.cs:35:7:35:7 | ; | normal | | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:31:21:31:32 | ... == ... | normal (break) | -| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | ; | normal (break) | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:31:21:31:32 | ... == ... | false [break] | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | ; | normal [break] | | BreakInTry.cs:22:22:22:24 | String arg | BreakInTry.cs:22:22:22:24 | String arg | normal | | BreakInTry.cs:22:29:22:32 | access to parameter args | BreakInTry.cs:22:29:22:32 | access to parameter args | normal | | BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:31:21:31:32 | ... == ... | break [false] | | BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:31:21:31:32 | ... == ... | false | -| BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:32:21:32:21 | ; | break | -| BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:32:21:32:21 | ; | false | +| BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:32:21:32:21 | ; | break [normal] | | BreakInTry.cs:23:9:34:9 | {...} | BreakInTry.cs:32:21:32:21 | ; | normal | | BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:31:21:31:32 | ... == ... | break [false] | | BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:31:21:31:32 | ... == ... | false | -| BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:32:21:32:21 | ; | break | -| BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:32:21:32:21 | ; | false | +| BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:32:21:32:21 | ; | break [normal] | | BreakInTry.cs:24:13:33:13 | try {...} ... | BreakInTry.cs:32:21:32:21 | ; | normal | | BreakInTry.cs:25:13:28:13 | {...} | BreakInTry.cs:26:21:26:31 | ... == ... | false | | BreakInTry.cs:25:13:28:13 | {...} | BreakInTry.cs:27:21:27:26 | break; | break | @@ -910,13 +904,12 @@ | BreakInTry.cs:32:21:32:21 | ; | BreakInTry.cs:32:21:32:21 | ; | normal | | BreakInTry.cs:35:7:35:7 | ; | BreakInTry.cs:35:7:35:7 | ; | normal | | BreakInTry.cs:39:5:54:5 | {...} | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | return [empty] | -| BreakInTry.cs:39:5:54:5 | {...} | BreakInTry.cs:50:21:50:26 | break; | return | +| BreakInTry.cs:39:5:54:5 | {...} | BreakInTry.cs:50:21:50:26 | break; | return [normal] | | BreakInTry.cs:39:5:54:5 | {...} | BreakInTry.cs:53:7:53:7 | ; | normal | | BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | empty | | BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | return [empty] | -| BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:50:21:50:26 | break; | false | -| BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:50:21:50:26 | break; | normal (break) | -| BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:50:21:50:26 | break; | return | +| BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:50:21:50:26 | break; | normal [break] | +| BreakInTry.cs:40:9:52:9 | try {...} ... | BreakInTry.cs:50:21:50:26 | break; | return [normal] | | BreakInTry.cs:41:9:44:9 | {...} | BreakInTry.cs:42:17:42:28 | ... == ... | false | | BreakInTry.cs:41:9:44:9 | {...} | BreakInTry.cs:43:17:43:23 | return ...; | return | | BreakInTry.cs:42:13:43:23 | if (...) ... | BreakInTry.cs:42:17:42:28 | ... == ... | false | @@ -927,9 +920,9 @@ | BreakInTry.cs:42:25:42:28 | null | BreakInTry.cs:42:25:42:28 | null | normal | | BreakInTry.cs:43:17:43:23 | return ...; | BreakInTry.cs:43:17:43:23 | return ...; | return | | BreakInTry.cs:46:9:52:9 | {...} | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:46:9:52:9 | {...} | BreakInTry.cs:50:21:50:26 | break; | normal (break) | +| BreakInTry.cs:46:9:52:9 | {...} | BreakInTry.cs:50:21:50:26 | break; | normal [break] | | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | break; | normal (break) | +| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | break; | normal [break] | | BreakInTry.cs:47:26:47:28 | String arg | BreakInTry.cs:47:26:47:28 | String arg | normal | | BreakInTry.cs:47:33:47:36 | access to parameter args | BreakInTry.cs:47:33:47:36 | access to parameter args | normal | | BreakInTry.cs:48:13:51:13 | {...} | BreakInTry.cs:49:21:49:31 | ... == ... | false | @@ -944,14 +937,12 @@ | BreakInTry.cs:53:7:53:7 | ; | BreakInTry.cs:53:7:53:7 | ; | normal | | BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | empty | | BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | return [empty] | -| BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:68:21:68:26 | break; | false | -| BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:68:21:68:26 | break; | normal (break) | -| BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:68:21:68:26 | break; | return | +| BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:68:21:68:26 | break; | normal [break] | +| BreakInTry.cs:57:5:71:5 | {...} | BreakInTry.cs:68:21:68:26 | break; | return [normal] | | BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | empty | | BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | return [empty] | -| BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:68:21:68:26 | break; | false | -| BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:68:21:68:26 | break; | normal (break) | -| BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:68:21:68:26 | break; | return | +| BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:68:21:68:26 | break; | normal [break] | +| BreakInTry.cs:58:9:70:9 | try {...} ... | BreakInTry.cs:68:21:68:26 | break; | return [normal] | | BreakInTry.cs:59:9:62:9 | {...} | BreakInTry.cs:60:17:60:28 | ... == ... | false | | BreakInTry.cs:59:9:62:9 | {...} | BreakInTry.cs:61:17:61:23 | return ...; | return | | BreakInTry.cs:60:13:61:23 | if (...) ... | BreakInTry.cs:60:17:60:28 | ... == ... | false | @@ -962,9 +953,9 @@ | BreakInTry.cs:60:25:60:28 | null | BreakInTry.cs:60:25:60:28 | null | normal | | BreakInTry.cs:61:17:61:23 | return ...; | BreakInTry.cs:61:17:61:23 | return ...; | return | | BreakInTry.cs:64:9:70:9 | {...} | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:64:9:70:9 | {...} | BreakInTry.cs:68:21:68:26 | break; | normal (break) | +| BreakInTry.cs:64:9:70:9 | {...} | BreakInTry.cs:68:21:68:26 | break; | normal [break] | | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | empty | -| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | break; | normal (break) | +| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | break; | normal [break] | | BreakInTry.cs:65:26:65:28 | String arg | BreakInTry.cs:65:26:65:28 | String arg | normal | | BreakInTry.cs:65:33:65:36 | access to parameter args | BreakInTry.cs:65:33:65:36 | access to parameter args | normal | | BreakInTry.cs:66:13:69:13 | {...} | BreakInTry.cs:67:21:67:31 | ... == ... | false | @@ -989,14 +980,14 @@ | CompileTimeOperators.cs:22:9:22:25 | return ...; | CompileTimeOperators.cs:22:9:22:25 | return ...; | return | | CompileTimeOperators.cs:22:16:22:24 | nameof(...) | CompileTimeOperators.cs:22:16:22:24 | nameof(...) | normal | | CompileTimeOperators.cs:22:23:22:23 | access to parameter i | CompileTimeOperators.cs:22:23:22:23 | access to parameter i | normal | -| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | goto(End) | -| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(Exception) | -| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(OutOfMemoryException) | +| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | goto(End) [normal] | +| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(Exception) [normal] | +| CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | CompileTimeOperators.cs:29:5:41:5 | {...} | CompileTimeOperators.cs:40:14:40:37 | call to method WriteLine | normal | -| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | goto(End) | +| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | goto(End) [normal] | | CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | normal | -| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(Exception) | -| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(OutOfMemoryException) | +| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(Exception) [normal] | +| CompileTimeOperators.cs:30:9:38:9 | try {...} ... | CompileTimeOperators.cs:37:13:37:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | CompileTimeOperators.cs:31:9:34:9 | {...} | CompileTimeOperators.cs:32:13:32:21 | goto ...; | goto(End) | | CompileTimeOperators.cs:31:9:34:9 | {...} | CompileTimeOperators.cs:33:13:33:37 | call to method WriteLine | normal | | CompileTimeOperators.cs:31:9:34:9 | {...} | CompileTimeOperators.cs:33:13:33:37 | call to method WriteLine | throw(Exception) | @@ -1546,9 +1537,9 @@ | ExitMethods.cs:88:9:88:28 | ...; | ExitMethods.cs:88:9:88:27 | call to method Exit | exit | | ExitMethods.cs:88:26:88:26 | 0 | ExitMethods.cs:88:26:88:26 | 0 | normal | | ExitMethods.cs:92:5:102:5 | {...} | ExitMethods.cs:95:13:95:18 | call to method Exit | exit | -| ExitMethods.cs:92:5:102:5 | {...} | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit | +| ExitMethods.cs:92:5:102:5 | {...} | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit [normal] | | ExitMethods.cs:93:9:101:9 | try {...} ... | ExitMethods.cs:95:13:95:18 | call to method Exit | exit | -| ExitMethods.cs:93:9:101:9 | try {...} ... | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit | +| ExitMethods.cs:93:9:101:9 | try {...} ... | ExitMethods.cs:100:13:100:40 | call to method WriteLine | exit [normal] | | ExitMethods.cs:94:9:96:9 | {...} | ExitMethods.cs:95:13:95:18 | call to method Exit | exit | | ExitMethods.cs:95:13:95:18 | call to method Exit | ExitMethods.cs:95:13:95:18 | call to method Exit | exit | | ExitMethods.cs:95:13:95:18 | this access | ExitMethods.cs:95:13:95:18 | this access | normal | @@ -1645,11 +1636,11 @@ | Extensions.cs:25:23:25:32 | access to method Parse | Extensions.cs:25:23:25:32 | access to method Parse | normal | | Extensions.cs:25:23:25:32 | delegate creation of type Func | Extensions.cs:25:23:25:32 | delegate creation of type Func | normal | | Finally.cs:8:5:17:5 | {...} | Finally.cs:15:13:15:40 | call to method WriteLine | normal | -| Finally.cs:8:5:17:5 | {...} | Finally.cs:15:13:15:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:8:5:17:5 | {...} | Finally.cs:15:13:15:40 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:8:5:17:5 | {...} | Finally.cs:15:13:15:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:8:5:17:5 | {...} | Finally.cs:15:13:15:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:9:9:16:9 | try {...} ... | Finally.cs:15:13:15:40 | call to method WriteLine | normal | -| Finally.cs:9:9:16:9 | try {...} ... | Finally.cs:15:13:15:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:9:9:16:9 | try {...} ... | Finally.cs:15:13:15:40 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:9:9:16:9 | try {...} ... | Finally.cs:15:13:15:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:9:9:16:9 | try {...} ... | Finally.cs:15:13:15:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:10:9:12:9 | {...} | Finally.cs:11:13:11:37 | call to method WriteLine | normal | | Finally.cs:10:9:12:9 | {...} | Finally.cs:11:13:11:37 | call to method WriteLine | throw(Exception) | | Finally.cs:10:9:12:9 | {...} | Finally.cs:11:31:11:36 | "Try1" | throw(OutOfMemoryException) | @@ -1666,13 +1657,13 @@ | Finally.cs:15:13:15:41 | ...; | Finally.cs:15:13:15:40 | call to method WriteLine | normal | | Finally.cs:15:31:15:39 | "Finally" | Finally.cs:15:31:15:39 | "Finally" | normal | | Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | normal | -| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | return | -| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | throw(IOException) | +| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | return [normal] | +| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:20:5:52:5 | {...} | Finally.cs:50:13:50:40 | call to method WriteLine | throw(IOException) [normal] | | Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | normal | -| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | return | -| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | throw(IOException) | +| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | return [normal] | +| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:21:9:51:9 | try {...} ... | Finally.cs:50:13:50:40 | call to method WriteLine | throw(IOException) [normal] | | Finally.cs:22:9:25:9 | {...} | Finally.cs:23:13:23:37 | call to method WriteLine | throw(Exception) | | Finally.cs:22:9:25:9 | {...} | Finally.cs:23:31:23:36 | "Try2" | throw(OutOfMemoryException) | | Finally.cs:22:9:25:9 | {...} | Finally.cs:24:13:24:19 | return ...; | return | @@ -1715,15 +1706,15 @@ | Finally.cs:50:13:50:41 | ...; | Finally.cs:50:13:50:40 | call to method WriteLine | normal | | Finally.cs:50:31:50:39 | "Finally" | Finally.cs:50:31:50:39 | "Finally" | normal | | Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | normal | -| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | return | -| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(IOException) | -| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | return [normal] | +| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(IOException) [normal] | +| Finally.cs:55:5:72:5 | {...} | Finally.cs:70:13:70:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | normal | -| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | return | -| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(Exception) | -| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(IOException) | -| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | return [normal] | +| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(IOException) [normal] | +| Finally.cs:56:9:71:9 | try {...} ... | Finally.cs:70:13:70:40 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:57:9:60:9 | {...} | Finally.cs:58:13:58:37 | call to method WriteLine | throw(Exception) | | Finally.cs:57:9:60:9 | {...} | Finally.cs:58:31:58:36 | "Try3" | throw(OutOfMemoryException) | | Finally.cs:57:9:60:9 | {...} | Finally.cs:59:13:59:19 | return ...; | return | @@ -1759,40 +1750,30 @@ | Finally.cs:70:13:70:41 | ...; | Finally.cs:70:13:70:40 | call to method WriteLine | normal | | Finally.cs:70:31:70:39 | "Finally" | Finally.cs:70:31:70:39 | "Finally" | normal | | Finally.cs:75:5:101:5 | {...} | Finally.cs:77:16:77:20 | ... > ... | false | -| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | normal (break) | -| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | return | -| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | return [false] | -| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | +| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | normal [break] | +| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | return [normal] | +| Finally.cs:75:5:101:5 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | | Finally.cs:76:9:76:19 | ... ...; | Finally.cs:76:13:76:18 | Int32 i = ... | normal | | Finally.cs:76:13:76:18 | Int32 i = ... | Finally.cs:76:13:76:18 | Int32 i = ... | normal | | Finally.cs:76:17:76:18 | 10 | Finally.cs:76:17:76:18 | 10 | normal | | Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:77:16:77:20 | ... > ... | false | -| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | normal (break) | -| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | return | -| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | return [false] | -| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | +| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | normal [break] | +| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | return [normal] | +| Finally.cs:77:9:100:9 | while (...) ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | | Finally.cs:77:16:77:16 | access to local variable i | Finally.cs:77:16:77:16 | access to local variable i | normal | | Finally.cs:77:16:77:20 | ... > ... | Finally.cs:77:16:77:20 | ... > ... | false | | Finally.cs:77:16:77:20 | ... > ... | Finally.cs:77:16:77:20 | ... > ... | true | | Finally.cs:77:20:77:20 | 0 | Finally.cs:77:20:77:20 | 0 | normal | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | break | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | break [false] | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | continue | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | continue [false] | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | false | +| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | break [normal] | +| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | continue [normal] | | Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | normal | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | return | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | return [false] | -| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | break | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | break [false] | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | continue | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | continue [false] | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | false | +| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | return [normal] | +| Finally.cs:78:9:100:9 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | +| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | break [normal] | +| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | continue [normal] | | Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | normal | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | return | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | return [false] | -| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | +| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | return [normal] | +| Finally.cs:79:13:99:13 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | | Finally.cs:80:13:87:13 | {...} | Finally.cs:82:21:82:27 | return ...; | return | | Finally.cs:80:13:87:13 | {...} | Finally.cs:84:21:84:29 | continue; | continue | | Finally.cs:80:13:87:13 | {...} | Finally.cs:85:21:85:26 | ... == ... | false | @@ -1818,12 +1799,10 @@ | Finally.cs:85:21:85:26 | ... == ... | Finally.cs:85:21:85:26 | ... == ... | true | | Finally.cs:85:26:85:26 | 2 | Finally.cs:85:26:85:26 | 2 | normal | | Finally.cs:86:21:86:26 | break; | Finally.cs:86:21:86:26 | break; | break | -| Finally.cs:89:13:99:13 | {...} | Finally.cs:97:21:97:23 | ...-- | false | | Finally.cs:89:13:99:13 | {...} | Finally.cs:97:21:97:23 | ...-- | normal | -| Finally.cs:89:13:99:13 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | -| Finally.cs:90:17:98:17 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | false | +| Finally.cs:89:13:99:13 | {...} | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | | Finally.cs:90:17:98:17 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | normal | -| Finally.cs:90:17:98:17 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) | +| Finally.cs:90:17:98:17 | try {...} ... | Finally.cs:97:21:97:23 | ...-- | throw(Exception) [normal] | | Finally.cs:91:17:94:17 | {...} | Finally.cs:92:25:92:30 | ... == ... | false | | Finally.cs:91:17:94:17 | {...} | Finally.cs:93:25:93:46 | throw ...; | throw(Exception) | | Finally.cs:91:17:94:17 | {...} | Finally.cs:93:31:93:45 | object creation of type Exception | throw(Exception) | @@ -1847,23 +1826,21 @@ | Finally.cs:104:5:119:5 | {...} | Finally.cs:116:17:116:32 | ... > ... | throw(Exception) [false] | | Finally.cs:104:5:119:5 | {...} | Finally.cs:116:17:116:32 | ... > ... | throw(NullReferenceException) [false] | | Finally.cs:104:5:119:5 | {...} | Finally.cs:116:17:116:32 | ... > ... | throw(OutOfMemoryException) [false] | -| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | false | | Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | normal | -| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | return | -| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(Exception) | -| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(NullReferenceException) | -| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | return [normal] | +| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(NullReferenceException) [normal] | +| Finally.cs:104:5:119:5 | {...} | Finally.cs:117:17:117:36 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:116:17:116:32 | ... > ... | false | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:116:17:116:32 | ... > ... | return [false] | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:116:17:116:32 | ... > ... | throw(Exception) [false] | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:116:17:116:32 | ... > ... | throw(NullReferenceException) [false] | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:116:17:116:32 | ... > ... | throw(OutOfMemoryException) [false] | -| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | false | | Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | normal | -| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | return | -| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(Exception) | -| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(NullReferenceException) | -| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | return [normal] | +| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(NullReferenceException) [normal] | +| Finally.cs:105:9:118:9 | try {...} ... | Finally.cs:117:17:117:36 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:106:9:111:9 | {...} | Finally.cs:107:17:107:21 | access to field Field | throw(NullReferenceException) | | Finally.cs:106:9:111:9 | {...} | Finally.cs:107:17:107:28 | access to property Length | throw(Exception) | | Finally.cs:106:9:111:9 | {...} | Finally.cs:107:17:107:28 | access to property Length | throw(NullReferenceException) | @@ -1958,13 +1935,13 @@ | Finally.cs:128:9:130:9 | {...} | Finally.cs:129:13:129:13 | ; | normal | | Finally.cs:129:13:129:13 | ; | Finally.cs:129:13:129:13 | ; | normal | | Finally.cs:134:5:145:5 | {...} | Finally.cs:141:13:141:44 | throw ...; | throw(ArgumentException) | -| Finally.cs:134:5:145:5 | {...} | Finally.cs:142:13:142:37 | call to method WriteLine | throw(Exception) | -| Finally.cs:134:5:145:5 | {...} | Finally.cs:142:13:142:37 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:134:5:145:5 | {...} | Finally.cs:142:13:142:37 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:134:5:145:5 | {...} | Finally.cs:142:13:142:37 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:134:5:145:5 | {...} | Finally.cs:144:9:144:33 | call to method WriteLine | normal | | Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:141:13:141:44 | throw ...; | throw(ArgumentException) | | Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:142:13:142:37 | call to method WriteLine | normal | -| Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:142:13:142:37 | call to method WriteLine | throw(Exception) | -| Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:142:13:142:37 | call to method WriteLine | throw(OutOfMemoryException) | +| Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:142:13:142:37 | call to method WriteLine | throw(Exception) [normal] | +| Finally.cs:135:9:143:9 | try {...} ... | Finally.cs:142:13:142:37 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | Finally.cs:136:9:138:9 | {...} | Finally.cs:137:13:137:36 | call to method WriteLine | normal | | Finally.cs:136:9:138:9 | {...} | Finally.cs:137:13:137:36 | call to method WriteLine | throw(Exception) | | Finally.cs:136:9:138:9 | {...} | Finally.cs:137:31:137:35 | "Try" | throw(OutOfMemoryException) | @@ -1990,25 +1967,21 @@ | Finally.cs:148:5:170:5 | {...} | Finally.cs:158:21:158:36 | ... == ... | false | | Finally.cs:148:5:170:5 | {...} | Finally.cs:158:21:158:36 | ... == ... | throw(ArgumentNullException) [false] | | Finally.cs:148:5:170:5 | {...} | Finally.cs:158:21:158:36 | ... == ... | throw(Exception) [false] | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | false | | Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | normal | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | throw(ArgumentNullException) | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | throw(Exception) | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | false | +| Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | throw(ArgumentNullException) [normal] | +| Finally.cs:148:5:170:5 | {...} | Finally.cs:163:17:163:42 | call to method WriteLine | throw(Exception) [normal] | | Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | normal | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | throw(ArgumentNullException) | -| Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | throw(Exception) | +| Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | throw(ArgumentNullException) [normal] | +| Finally.cs:148:5:170:5 | {...} | Finally.cs:167:17:167:37 | call to method WriteLine | throw(Exception) [normal] | | Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:158:21:158:36 | ... == ... | false | | Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:158:21:158:36 | ... == ... | throw(ArgumentNullException) [false] | | Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:158:21:158:36 | ... == ... | throw(Exception) [false] | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | false | | Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | normal | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | throw(ArgumentNullException) | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | throw(Exception) | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | false | +| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | throw(ArgumentNullException) [normal] | +| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:163:17:163:42 | call to method WriteLine | throw(Exception) [normal] | | Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | normal | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | throw(ArgumentNullException) | -| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | throw(Exception) | +| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | throw(ArgumentNullException) [normal] | +| Finally.cs:149:9:169:9 | try {...} ... | Finally.cs:167:17:167:37 | call to method WriteLine | throw(Exception) [normal] | | Finally.cs:150:9:153:9 | {...} | Finally.cs:151:17:151:28 | ... == ... | false | | Finally.cs:150:9:153:9 | {...} | Finally.cs:152:17:152:50 | throw ...; | throw(ArgumentNullException) | | Finally.cs:150:9:153:9 | {...} | Finally.cs:152:23:152:49 | object creation of type ArgumentNullException | throw(Exception) | @@ -2157,16 +2130,15 @@ | Finally.cs:196:5:214:5 | {...} | Finally.cs:209:21:209:22 | access to parameter b3 | throw(Exception) [false] | | Finally.cs:196:5:214:5 | {...} | Finally.cs:209:21:209:22 | access to parameter b3 | throw(ExceptionB) [false] | | Finally.cs:196:5:214:5 | {...} | Finally.cs:209:25:209:47 | throw ...; | throw(ExceptionC) | -| Finally.cs:196:5:214:5 | {...} | Finally.cs:211:13:211:28 | ... = ... | throw(Exception) | -| Finally.cs:196:5:214:5 | {...} | Finally.cs:211:13:211:28 | ... = ... | throw(ExceptionA) | +| Finally.cs:196:5:214:5 | {...} | Finally.cs:211:13:211:28 | ... = ... | throw(Exception) [normal] | +| Finally.cs:196:5:214:5 | {...} | Finally.cs:211:13:211:28 | ... = ... | throw(ExceptionA) [normal] | | Finally.cs:196:5:214:5 | {...} | Finally.cs:213:9:213:24 | ... = ... | normal | | Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:209:21:209:22 | access to parameter b3 | throw(Exception) [false] | | Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:209:21:209:22 | access to parameter b3 | throw(ExceptionB) [false] | | Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:209:25:209:47 | throw ...; | throw(ExceptionC) | -| Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | false | | Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | normal | -| Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | throw(Exception) | -| Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | throw(ExceptionA) | +| Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | throw(Exception) [normal] | +| Finally.cs:197:9:212:9 | try {...} ... | Finally.cs:211:13:211:28 | ... = ... | throw(ExceptionA) [normal] | | Finally.cs:198:9:200:9 | {...} | Finally.cs:199:17:199:18 | access to parameter b1 | false | | Finally.cs:198:9:200:9 | {...} | Finally.cs:199:21:199:43 | throw ...; | throw(ExceptionA) | | Finally.cs:198:9:200:9 | {...} | Finally.cs:199:27:199:42 | object creation of type ExceptionA | throw(Exception) | @@ -2832,12 +2804,12 @@ | Patterns.cs:16:18:16:28 | ... is ... | Patterns.cs:16:18:16:28 | ... is ... | true | | Patterns.cs:16:23:16:28 | Object v1 | Patterns.cs:16:23:16:28 | Object v1 | normal | | Patterns.cs:17:9:18:9 | {...} | Patterns.cs:17:9:18:9 | {...} | normal | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:23:17:23:22 | break; | normal (break) | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:26:17:26:22 | break; | normal (break) | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:29:17:29:22 | break; | normal (break) | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:32:17:32:22 | break; | normal (break) | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:34:17:34:22 | break; | normal (break) | -| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:37:17:37:22 | break; | normal (break) | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:23:17:23:22 | break; | normal [break] | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:26:17:26:22 | break; | normal [break] | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:29:17:29:22 | break; | normal [break] | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:32:17:32:22 | break; | normal [break] | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:34:17:34:22 | break; | normal [break] | +| Patterns.cs:20:9:38:9 | switch (...) {...} | Patterns.cs:37:17:37:22 | break; | normal [break] | | Patterns.cs:20:17:20:17 | access to local variable o | Patterns.cs:20:17:20:17 | access to local variable o | normal | | Patterns.cs:22:13:22:23 | case ...: | Patterns.cs:22:18:22:22 | "xyz" | no-match | | Patterns.cs:22:13:22:23 | case ...: | Patterns.cs:23:17:23:22 | break; | break | @@ -3064,14 +3036,14 @@ | Switch.cs:37:17:37:23 | call to method Throw | Switch.cs:37:17:37:23 | call to method Throw | throw(Exception) | | Switch.cs:39:13:39:20 | default: | Switch.cs:40:17:40:23 | return ...; | return | | Switch.cs:40:17:40:23 | return ...; | Switch.cs:40:17:40:23 | return ...; | return | -| Switch.cs:45:5:53:5 | {...} | Switch.cs:49:17:49:22 | break; | normal (break) | +| Switch.cs:45:5:53:5 | {...} | Switch.cs:49:17:49:22 | break; | normal [break] | | Switch.cs:45:5:53:5 | {...} | Switch.cs:50:18:50:21 | access to type Boolean | no-match | | Switch.cs:45:5:53:5 | {...} | Switch.cs:50:30:50:38 | ... != ... | false | -| Switch.cs:45:5:53:5 | {...} | Switch.cs:51:17:51:22 | break; | normal (break) | -| Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:49:17:49:22 | break; | normal (break) | +| Switch.cs:45:5:53:5 | {...} | Switch.cs:51:17:51:22 | break; | normal [break] | +| Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:49:17:49:22 | break; | normal [break] | | Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:50:18:50:21 | access to type Boolean | no-match | | Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:50:30:50:38 | ... != ... | false | -| Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:51:17:51:22 | break; | normal (break) | +| Switch.cs:46:9:52:9 | switch (...) {...} | Switch.cs:51:17:51:22 | break; | normal [break] | | Switch.cs:46:17:46:17 | access to parameter o | Switch.cs:46:17:46:17 | access to parameter o | normal | | Switch.cs:48:13:48:23 | case ...: | Switch.cs:48:18:48:20 | access to type Int32 | no-match | | Switch.cs:48:13:48:23 | case ...: | Switch.cs:49:17:49:22 | break; | break | @@ -3088,10 +3060,10 @@ | Switch.cs:50:30:50:38 | ... != ... | Switch.cs:50:30:50:38 | ... != ... | true | | Switch.cs:50:35:50:38 | null | Switch.cs:50:35:50:38 | null | normal | | Switch.cs:51:17:51:22 | break; | Switch.cs:51:17:51:22 | break; | break | -| Switch.cs:56:5:64:5 | {...} | Switch.cs:60:17:60:22 | break; | normal (break) | -| Switch.cs:56:5:64:5 | {...} | Switch.cs:62:17:62:22 | break; | normal (break) | -| Switch.cs:57:9:63:9 | switch (...) {...} | Switch.cs:60:17:60:22 | break; | normal (break) | -| Switch.cs:57:9:63:9 | switch (...) {...} | Switch.cs:62:17:62:22 | break; | normal (break) | +| Switch.cs:56:5:64:5 | {...} | Switch.cs:60:17:60:22 | break; | normal [break] | +| Switch.cs:56:5:64:5 | {...} | Switch.cs:62:17:62:22 | break; | normal [break] | +| Switch.cs:57:9:63:9 | switch (...) {...} | Switch.cs:60:17:60:22 | break; | normal [break] | +| Switch.cs:57:9:63:9 | switch (...) {...} | Switch.cs:62:17:62:22 | break; | normal [break] | | Switch.cs:57:17:57:17 | 1 | Switch.cs:57:17:57:17 | 1 | normal | | Switch.cs:57:17:57:21 | ... + ... | Switch.cs:57:17:57:21 | ... + ... | normal | | Switch.cs:57:21:57:21 | 2 | Switch.cs:57:21:57:21 | 2 | normal | @@ -3102,12 +3074,12 @@ | Switch.cs:61:13:61:19 | case ...: | Switch.cs:62:17:62:22 | break; | break | | Switch.cs:61:18:61:18 | 3 | Switch.cs:61:18:61:18 | 3 | match | | Switch.cs:62:17:62:22 | break; | Switch.cs:62:17:62:22 | break; | break | -| Switch.cs:67:5:75:5 | {...} | Switch.cs:71:17:71:22 | break; | normal (break) | +| Switch.cs:67:5:75:5 | {...} | Switch.cs:71:17:71:22 | break; | normal [break] | | Switch.cs:67:5:75:5 | {...} | Switch.cs:72:18:72:19 | "" | no-match | -| Switch.cs:67:5:75:5 | {...} | Switch.cs:73:17:73:22 | break; | normal (break) | -| Switch.cs:68:9:74:9 | switch (...) {...} | Switch.cs:71:17:71:22 | break; | normal (break) | +| Switch.cs:67:5:75:5 | {...} | Switch.cs:73:17:73:22 | break; | normal [break] | +| Switch.cs:68:9:74:9 | switch (...) {...} | Switch.cs:71:17:71:22 | break; | normal [break] | | Switch.cs:68:9:74:9 | switch (...) {...} | Switch.cs:72:18:72:19 | "" | no-match | -| Switch.cs:68:9:74:9 | switch (...) {...} | Switch.cs:73:17:73:22 | break; | normal (break) | +| Switch.cs:68:9:74:9 | switch (...) {...} | Switch.cs:73:17:73:22 | break; | normal [break] | | Switch.cs:68:17:68:25 | (...) ... | Switch.cs:68:17:68:25 | (...) ... | normal | | Switch.cs:68:25:68:25 | access to parameter s | Switch.cs:68:25:68:25 | access to parameter s | normal | | Switch.cs:70:13:70:23 | case ...: | Switch.cs:70:18:70:20 | access to type Int32 | no-match | @@ -3124,7 +3096,7 @@ | Switch.cs:78:5:89:5 | {...} | Switch.cs:88:9:88:21 | return ...; | return | | Switch.cs:79:9:87:9 | switch (...) {...} | Switch.cs:82:17:82:28 | return ...; | return | | Switch.cs:79:9:87:9 | switch (...) {...} | Switch.cs:83:18:83:18 | 2 | no-match | -| Switch.cs:79:9:87:9 | switch (...) {...} | Switch.cs:85:21:85:26 | break; | normal (break) | +| Switch.cs:79:9:87:9 | switch (...) {...} | Switch.cs:85:21:85:26 | break; | normal [break] | | Switch.cs:79:9:87:9 | switch (...) {...} | Switch.cs:86:17:86:28 | return ...; | return | | Switch.cs:79:17:79:17 | access to parameter i | Switch.cs:79:17:79:17 | access to parameter i | normal | | Switch.cs:81:13:81:19 | case ...: | Switch.cs:81:18:81:18 | 1 | no-match | @@ -3503,7 +3475,7 @@ | cflow.cs:38:5:68:5 | {...} | cflow.cs:64:21:64:55 | throw ...; | throw(NullReferenceException) | | cflow.cs:38:5:68:5 | {...} | cflow.cs:67:9:67:17 | return ...; | return | | cflow.cs:39:9:50:9 | switch (...) {...} | cflow.cs:47:18:47:18 | 3 | no-match | -| cflow.cs:39:9:50:9 | switch (...) {...} | cflow.cs:49:17:49:22 | break; | normal (break) | +| cflow.cs:39:9:50:9 | switch (...) {...} | cflow.cs:49:17:49:22 | break; | normal [break] | | cflow.cs:39:17:39:17 | access to parameter a | cflow.cs:39:17:39:17 | access to parameter a | normal | | cflow.cs:41:13:41:19 | case ...: | cflow.cs:41:18:41:18 | 1 | no-match | | cflow.cs:41:13:41:19 | case ...: | cflow.cs:42:17:42:38 | call to method WriteLine | normal | @@ -3531,8 +3503,8 @@ | cflow.cs:48:17:48:39 | ...; | cflow.cs:48:17:48:38 | call to method WriteLine | normal | | cflow.cs:48:35:48:37 | "3" | cflow.cs:48:35:48:37 | "3" | normal | | cflow.cs:49:17:49:22 | break; | cflow.cs:49:17:49:22 | break; | break | -| cflow.cs:51:9:59:9 | switch (...) {...} | cflow.cs:55:17:55:22 | break; | normal (break) | -| cflow.cs:51:9:59:9 | switch (...) {...} | cflow.cs:58:17:58:22 | break; | normal (break) | +| cflow.cs:51:9:59:9 | switch (...) {...} | cflow.cs:55:17:55:22 | break; | normal [break] | +| cflow.cs:51:9:59:9 | switch (...) {...} | cflow.cs:58:17:58:22 | break; | normal [break] | | cflow.cs:51:17:51:17 | access to parameter a | cflow.cs:51:17:51:17 | access to parameter a | normal | | cflow.cs:53:13:53:20 | case ...: | cflow.cs:53:18:53:19 | 42 | no-match | | cflow.cs:53:13:53:20 | case ...: | cflow.cs:54:17:54:47 | call to method WriteLine | normal | @@ -3549,7 +3521,7 @@ | cflow.cs:58:17:58:22 | break; | cflow.cs:58:17:58:22 | break; | break | | cflow.cs:60:9:66:9 | switch (...) {...} | cflow.cs:62:18:62:18 | 0 | no-match | | cflow.cs:60:9:66:9 | switch (...) {...} | cflow.cs:64:21:64:55 | throw ...; | throw(NullReferenceException) | -| cflow.cs:60:9:66:9 | switch (...) {...} | cflow.cs:65:17:65:22 | break; | normal (break) | +| cflow.cs:60:9:66:9 | switch (...) {...} | cflow.cs:65:17:65:22 | break; | normal [break] | | cflow.cs:60:17:60:32 | call to method Parse | cflow.cs:60:17:60:32 | call to method Parse | normal | | cflow.cs:60:27:60:31 | access to field Field | cflow.cs:60:27:60:31 | access to field Field | normal | | cflow.cs:60:27:60:31 | this access | cflow.cs:60:27:60:31 | this access | normal | @@ -3754,7 +3726,7 @@ | cflow.cs:150:13:150:32 | call to method WriteLine | cflow.cs:150:13:150:32 | call to method WriteLine | normal | | cflow.cs:150:13:150:33 | ...; | cflow.cs:150:13:150:32 | call to method WriteLine | normal | | cflow.cs:150:31:150:31 | access to local variable x | cflow.cs:150:31:150:31 | access to local variable x | normal | -| cflow.cs:152:9:157:9 | for (...;...;...) ... | cflow.cs:156:17:156:22 | break; | normal (break) | +| cflow.cs:152:9:157:9 | for (...;...;...) ... | cflow.cs:156:17:156:22 | break; | normal [break] | | cflow.cs:152:18:152:18 | access to local variable x | cflow.cs:152:18:152:18 | access to local variable x | normal | | cflow.cs:152:18:152:20 | ...++ | cflow.cs:152:18:152:20 | ...++ | normal | | cflow.cs:153:9:157:9 | {...} | cflow.cs:155:17:155:22 | ... > ... | false | @@ -3769,7 +3741,7 @@ | cflow.cs:155:17:155:22 | ... > ... | cflow.cs:155:17:155:22 | ... > ... | true | | cflow.cs:155:21:155:22 | 20 | cflow.cs:155:21:155:22 | 20 | normal | | cflow.cs:156:17:156:22 | break; | cflow.cs:156:17:156:22 | break; | break | -| cflow.cs:159:9:165:9 | for (...;...;...) ... | cflow.cs:164:17:164:22 | break; | normal (break) | +| cflow.cs:159:9:165:9 | for (...;...;...) ... | cflow.cs:164:17:164:22 | break; | normal [break] | | cflow.cs:160:9:165:9 | {...} | cflow.cs:163:17:163:22 | ... > ... | false | | cflow.cs:160:9:165:9 | {...} | cflow.cs:164:17:164:22 | break; | break | | cflow.cs:161:13:161:32 | call to method WriteLine | cflow.cs:161:13:161:32 | call to method WriteLine | normal | @@ -3930,9 +3902,9 @@ | cflow.cs:202:13:204:13 | {...} | cflow.cs:203:17:203:38 | throw ...; | throw(Exception) | | cflow.cs:203:17:203:38 | throw ...; | cflow.cs:203:17:203:38 | throw ...; | throw(Exception) | | cflow.cs:203:23:203:37 | object creation of type Exception | cflow.cs:203:23:203:37 | object creation of type Exception | normal | -| cflow.cs:209:5:222:5 | {...} | cflow.cs:219:17:219:22 | break; | normal (break) | +| cflow.cs:209:5:222:5 | {...} | cflow.cs:219:17:219:22 | break; | normal [break] | | cflow.cs:209:5:222:5 | {...} | cflow.cs:221:18:221:34 | ... < ... | false | -| cflow.cs:210:9:221:36 | do ... while (...); | cflow.cs:219:17:219:22 | break; | normal (break) | +| cflow.cs:210:9:221:36 | do ... while (...); | cflow.cs:219:17:219:22 | break; | normal [break] | | cflow.cs:210:9:221:36 | do ... while (...); | cflow.cs:221:18:221:34 | ... < ... | false | | cflow.cs:211:9:221:9 | {...} | cflow.cs:215:17:215:25 | continue; | continue | | cflow.cs:211:9:221:9 | {...} | cflow.cs:217:17:217:32 | ... < ... | false | @@ -3973,9 +3945,9 @@ | cflow.cs:221:18:221:34 | ... < ... | cflow.cs:221:18:221:34 | ... < ... | true | | cflow.cs:221:33:221:34 | 10 | cflow.cs:221:33:221:34 | 10 | normal | | cflow.cs:225:5:238:5 | {...} | cflow.cs:226:9:237:9 | foreach (... ... in ...) ... | empty | -| cflow.cs:225:5:238:5 | {...} | cflow.cs:235:17:235:22 | break; | normal (break) | +| cflow.cs:225:5:238:5 | {...} | cflow.cs:235:17:235:22 | break; | normal [break] | | cflow.cs:226:9:237:9 | foreach (... ... in ...) ... | cflow.cs:226:9:237:9 | foreach (... ... in ...) ... | empty | -| cflow.cs:226:9:237:9 | foreach (... ... in ...) ... | cflow.cs:235:17:235:22 | break; | normal (break) | +| cflow.cs:226:9:237:9 | foreach (... ... in ...) ... | cflow.cs:235:17:235:22 | break; | normal [break] | | cflow.cs:226:22:226:22 | String x | cflow.cs:226:22:226:22 | String x | normal | | cflow.cs:226:27:226:64 | call to method Repeat | cflow.cs:226:27:226:64 | call to method Repeat | normal | | cflow.cs:226:57:226:59 | "a" | cflow.cs:226:57:226:59 | "a" | normal | @@ -4013,9 +3985,9 @@ | cflow.cs:234:13:236:13 | {...} | cflow.cs:235:17:235:22 | break; | break | | cflow.cs:235:17:235:22 | break; | cflow.cs:235:17:235:22 | break; | break | | cflow.cs:241:5:259:5 | {...} | cflow.cs:244:31:244:41 | goto ...; | goto(Label) | -| cflow.cs:241:5:259:5 | {...} | cflow.cs:252:17:252:22 | break; | normal (break) | +| cflow.cs:241:5:259:5 | {...} | cflow.cs:252:17:252:22 | break; | normal [break] | | cflow.cs:241:5:259:5 | {...} | cflow.cs:254:17:254:27 | goto ...; | goto(Label) | -| cflow.cs:241:5:259:5 | {...} | cflow.cs:257:17:257:22 | break; | normal (break) | +| cflow.cs:241:5:259:5 | {...} | cflow.cs:257:17:257:22 | break; | normal [break] | | cflow.cs:242:9:242:13 | Label: | cflow.cs:242:9:242:13 | Label: | normal | | cflow.cs:242:16:242:45 | if (...) ... | cflow.cs:242:20:242:40 | !... | false | | cflow.cs:242:16:242:45 | if (...) ... | cflow.cs:242:43:242:45 | {...} | normal | @@ -4039,9 +4011,9 @@ | cflow.cs:244:13:244:28 | ... > ... | cflow.cs:244:13:244:28 | ... > ... | true | | cflow.cs:244:28:244:28 | 0 | cflow.cs:244:28:244:28 | 0 | normal | | cflow.cs:244:31:244:41 | goto ...; | cflow.cs:244:31:244:41 | goto ...; | goto(Label) | -| cflow.cs:246:9:258:9 | switch (...) {...} | cflow.cs:252:17:252:22 | break; | normal (break) | +| cflow.cs:246:9:258:9 | switch (...) {...} | cflow.cs:252:17:252:22 | break; | normal [break] | | cflow.cs:246:9:258:9 | switch (...) {...} | cflow.cs:254:17:254:27 | goto ...; | goto(Label) | -| cflow.cs:246:9:258:9 | switch (...) {...} | cflow.cs:257:17:257:22 | break; | normal (break) | +| cflow.cs:246:9:258:9 | switch (...) {...} | cflow.cs:257:17:257:22 | break; | normal [break] | | cflow.cs:246:17:246:21 | access to field Field | cflow.cs:246:17:246:21 | access to field Field | normal | | cflow.cs:246:17:246:21 | this access | cflow.cs:246:17:246:21 | this access | normal | | cflow.cs:246:17:246:28 | access to property Length | cflow.cs:246:17:246:28 | access to property Length | normal | @@ -4071,9 +4043,9 @@ | cflow.cs:256:35:256:35 | 0 | cflow.cs:256:35:256:35 | 0 | normal | | cflow.cs:257:17:257:22 | break; | cflow.cs:257:17:257:22 | break; | break | | cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | normal | -| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | return | -| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | throw(Exception) | -| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | throw(OutOfMemoryException) | +| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | return [normal] | +| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | throw(Exception) [normal] | +| cflow.cs:262:5:277:5 | {...} | cflow.cs:275:13:275:41 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | cflow.cs:263:9:263:23 | yield return ...; | cflow.cs:263:9:263:23 | yield return ...; | normal | | cflow.cs:263:22:263:22 | 0 | cflow.cs:263:22:263:22 | 0 | normal | | cflow.cs:264:9:267:9 | for (...;...;...) ... | cflow.cs:264:25:264:30 | ... < ... | false | @@ -4089,9 +4061,9 @@ | cflow.cs:266:13:266:27 | yield return ...; | cflow.cs:266:13:266:27 | yield return ...; | normal | | cflow.cs:266:26:266:26 | access to local variable i | cflow.cs:266:26:266:26 | access to local variable i | normal | | cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | normal | -| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | return | -| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | throw(Exception) | -| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | throw(OutOfMemoryException) | +| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | return [normal] | +| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | throw(Exception) [normal] | +| cflow.cs:268:9:276:9 | try {...} ... | cflow.cs:275:13:275:41 | call to method WriteLine | throw(OutOfMemoryException) [normal] | | cflow.cs:269:9:272:9 | {...} | cflow.cs:270:13:270:24 | yield break; | return | | cflow.cs:269:9:272:9 | {...} | cflow.cs:271:13:271:42 | call to method WriteLine | normal | | cflow.cs:269:9:272:9 | {...} | cflow.cs:271:13:271:42 | call to method WriteLine | throw(Exception) | diff --git a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected index b26ac444287..0dab813fa6c 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected @@ -1030,7 +1030,7 @@ | BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | semmle.label | false | | BreakInTry.cs:31:21:31:32 | ... == ... | BreakInTry.cs:32:21:32:21 | ; | semmle.label | true | | BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:32:21:32:21 | [finally: break] ; | semmle.label | true | -| BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:35:7:35:7 | ; | semmle.label | break | +| BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | BreakInTry.cs:35:7:35:7 | ; | semmle.label | false | | BreakInTry.cs:31:29:31:32 | [finally: break] null | BreakInTry.cs:31:21:31:32 | [finally: break] ... == ... | semmle.label | successor | | BreakInTry.cs:31:29:31:32 | null | BreakInTry.cs:31:21:31:32 | ... == ... | semmle.label | successor | | BreakInTry.cs:32:21:32:21 | ; | BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | semmle.label | successor | From caf73e4b9b5a476e43d2e8b48ddc62637ac3694b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 24 Nov 2020 18:16:56 +0100 Subject: [PATCH 59/97] Python: Wrap all Stdlib modeling consistently Some of these predicates had fallen outside the `private module Stdlib` --- .../src/semmle/python/frameworks/Stdlib.qll | 282 +++++++++--------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index c804ff99701..7603ee20833 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -728,165 +728,165 @@ private module Stdlib { ) } } -} -/** - * An exec statement (only Python 2). - * Se ehttps://docs.python.org/2/reference/simple_stmts.html#the-exec-statement. - */ -private class ExecStatement extends CodeExecution::Range { - ExecStatement() { - // since there are no DataFlow::Nodes for a Statement, we can't do anything like - // `this = any(Exec exec)` - this.asExpr() = any(Exec exec).getBody() + /** + * An exec statement (only Python 2). + * Se ehttps://docs.python.org/2/reference/simple_stmts.html#the-exec-statement. + */ + private class ExecStatement extends CodeExecution::Range { + ExecStatement() { + // since there are no DataFlow::Nodes for a Statement, we can't do anything like + // `this = any(Exec exec)` + this.asExpr() = any(Exec exec).getBody() + } + + override DataFlow::Node getCode() { result = this } } - override DataFlow::Node getCode() { result = this } -} + /** + * A call to the builtin `open` function. + * See https://docs.python.org/3/library/functions.html#open + */ + private class OpenCall extends FileSystemAccess::Range, DataFlow::CfgNode { + override CallNode node; -/** - * A call to the builtin `open` function. - * See https://docs.python.org/3/library/functions.html#open - */ -private class OpenCall extends FileSystemAccess::Range, DataFlow::CfgNode { - override CallNode node; + OpenCall() { node.getFunction().(NameNode).getId() = "open" } - OpenCall() { node.getFunction().(NameNode).getId() = "open" } - - override DataFlow::Node getAPathArgument() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("file")] + override DataFlow::Node getAPathArgument() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("file")] + } } -} -// --------------------------------------------------------------------------- -// base64 -// --------------------------------------------------------------------------- -/** Gets a reference to the `base64` module. */ -private DataFlow::Node base64(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("base64") - or - exists(DataFlow::TypeTracker t2 | result = base64(t2).track(t2, t)) -} - -/** Gets a reference to the `base64` module. */ -DataFlow::Node base64() { result = base64(DataFlow::TypeTracker::end()) } - -/** - * Gets a reference to the attribute `attr_name` of the `base64` module. - * WARNING: Only holds for a few predefined attributes. - */ -private DataFlow::Node base64_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in [ - "b64encode", "b64decode", "standard_b64encode", "standard_b64decode", "urlsafe_b64encode", - "urlsafe_b64decode", "b32encode", "b32decode", "b16encode", "b16decode", "encodestring", - "decodestring", "a85encode", "a85decode", "b85encode", "b85decode", "encodebytes", - "decodebytes" - ] and - ( + // --------------------------------------------------------------------------- + // base64 + // --------------------------------------------------------------------------- + /** Gets a reference to the `base64` module. */ + private DataFlow::Node base64(DataFlow::TypeTracker t) { t.start() and - result = DataFlow::importNode("base64" + "." + attr_name) + result = DataFlow::importNode("base64") or - t.startInAttr(attr_name) and - result = base64() - ) - or - // Due to bad performance when using normal setup with `base64_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - base64_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) + exists(DataFlow::TypeTracker t2 | result = base64(t2).track(t2, t)) + } + + /** Gets a reference to the `base64` module. */ + DataFlow::Node base64() { result = base64(DataFlow::TypeTracker::end()) } + + /** + * Gets a reference to the attribute `attr_name` of the `base64` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node base64_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in [ + "b64encode", "b64decode", "standard_b64encode", "standard_b64decode", "urlsafe_b64encode", + "urlsafe_b64decode", "b32encode", "b32decode", "b16encode", "b16decode", "encodestring", + "decodestring", "a85encode", "a85decode", "b85encode", "b85decode", "encodebytes", + "decodebytes" + ] and + ( + t.start() and + result = DataFlow::importNode("base64" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = base64() ) - ) -} - -pragma[nomagic] -private predicate base64_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary -) { - DataFlow::StepSummary::step(base64_attr(t2, attr_name), res, summary) -} - -/** - * Gets a reference to the attribute `attr_name` of the `base64` module. - * WARNING: Only holds for a few predefined attributes. - */ -private DataFlow::Node base64_attr(string attr_name) { - result = base64_attr(DataFlow::TypeTracker::end(), attr_name) -} - -/** A call to any of the encode functions in the `base64` module. */ -private class Base64EncodeCall extends Encoding::Range, DataFlow::CfgNode { - override CallNode node; - - Base64EncodeCall() { - exists(string name | - name in [ - "b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode", - "encodestring", "a85encode", "b85encode", "encodebytes" - ] and - node.getFunction() = base64_attr(name).asCfgNode() + or + // Due to bad performance when using normal setup with `base64_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + base64_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) ) } - override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } - - override DataFlow::Node getOutput() { result = this } - - override string getFormat() { - exists(string name | node.getFunction() = base64_attr(name).asCfgNode() | - name in [ - "b64encode", "standard_b64encode", "urlsafe_b64encode", "encodestring", "encodebytes" - ] and - result = "Base64" - or - name = "b32encode" and result = "Base32" - or - name = "b16encode" and result = "Base16" - or - name = "a85encode" and result = "Ascii85" - or - name = "b85encode" and result = "Base85" - ) - } -} - -/** A call to any of the decode functions in the `base64` module. */ -private class Base64DecodeCall extends Decoding::Range, DataFlow::CfgNode { - override CallNode node; - - Base64DecodeCall() { - exists(string name | - name in [ - "b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode", - "decodestring", "a85decode", "b85decode", "decodebytes" - ] and - node.getFunction() = base64_attr(name).asCfgNode() - ) + pragma[nomagic] + private predicate base64_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(base64_attr(t2, attr_name), res, summary) } - override predicate mayExecuteInput() { none() } + /** + * Gets a reference to the attribute `attr_name` of the `base64` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node base64_attr(string attr_name) { + result = base64_attr(DataFlow::TypeTracker::end(), attr_name) + } - override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } + /** A call to any of the encode functions in the `base64` module. */ + private class Base64EncodeCall extends Encoding::Range, DataFlow::CfgNode { + override CallNode node; - override DataFlow::Node getOutput() { result = this } + Base64EncodeCall() { + exists(string name | + name in [ + "b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode", + "encodestring", "a85encode", "b85encode", "encodebytes" + ] and + node.getFunction() = base64_attr(name).asCfgNode() + ) + } - override string getFormat() { - exists(string name | node.getFunction() = base64_attr(name).asCfgNode() | - name in [ - "b64decode", "standard_b64decode", "urlsafe_b64decode", "decodestring", "decodebytes" - ] and - result = "Base64" - or - name = "b32decode" and result = "Base32" - or - name = "b16decode" and result = "Base16" - or - name = "a85decode" and result = "Ascii85" - or - name = "b85decode" and result = "Base85" - ) + override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { + exists(string name | node.getFunction() = base64_attr(name).asCfgNode() | + name in [ + "b64encode", "standard_b64encode", "urlsafe_b64encode", "encodestring", "encodebytes" + ] and + result = "Base64" + or + name = "b32encode" and result = "Base32" + or + name = "b16encode" and result = "Base16" + or + name = "a85encode" and result = "Ascii85" + or + name = "b85encode" and result = "Base85" + ) + } + } + + /** A call to any of the decode functions in the `base64` module. */ + private class Base64DecodeCall extends Decoding::Range, DataFlow::CfgNode { + override CallNode node; + + Base64DecodeCall() { + exists(string name | + name in [ + "b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode", + "decodestring", "a85decode", "b85decode", "decodebytes" + ] and + node.getFunction() = base64_attr(name).asCfgNode() + ) + } + + override predicate mayExecuteInput() { none() } + + override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { + exists(string name | node.getFunction() = base64_attr(name).asCfgNode() | + name in [ + "b64decode", "standard_b64decode", "urlsafe_b64decode", "decodestring", "decodebytes" + ] and + result = "Base64" + or + name = "b32decode" and result = "Base32" + or + name = "b16decode" and result = "Base16" + or + name = "a85decode" and result = "Ascii85" + or + name = "b85decode" and result = "Base85" + ) + } } } From 5af1fdd06fdf5de33145926c872d30316e6730ff Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 24 Nov 2020 18:19:18 +0100 Subject: [PATCH 60/97] Python: Expand tests of `open` --- .../frameworks/stdlib/FileSystemAccess.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py index 975f2c24bcf..103911aebdf 100644 --- a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py +++ b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py @@ -1,3 +1,6 @@ +import builtins +import io + open("filepath") # $getAPathArgument="filepath" open(file="filepath") # $getAPathArgument="filepath" @@ -5,3 +8,11 @@ o = open o("filepath") # $ MISSING: getAPathArgument="filepath" o(file="filepath") # $ MISSING: getAPathArgument="filepath" + + +builtins.open("filepath") # $ MISSING: getAPathArgument="filepath" +builtins.open(file="filepath") # $ MISSING: getAPathArgument="filepath" + + +io.open("filepath") # $ MISSING: getAPathArgument="filepath" +io.open(file="filepath") # $ MISSING: getAPathArgument="filepath" From e39bb560786c044e7759f423b76fcb89d667b254 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 24 Nov 2020 18:23:30 +0100 Subject: [PATCH 61/97] Python: Model builtin `open` function better --- .../src/semmle/python/frameworks/Stdlib.qll | 30 +++++++++---------- .../frameworks/stdlib/FileSystemAccess.py | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index 7603ee20833..b4813fdb031 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -651,7 +651,7 @@ private module Stdlib { * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node builtins_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["exec", "eval", "compile"] and + attr_name in ["exec", "eval", "compile", "open"] and ( t.start() and result = DataFlow::importNode(["builtins", "__builtin__"] + "." + attr_name) @@ -729,6 +729,20 @@ private module Stdlib { } } + /** + * A call to the builtin `open` function. + * See https://docs.python.org/3/library/functions.html#open + */ + private class OpenCall extends FileSystemAccess::Range, DataFlow::CfgNode { + override CallNode node; + + OpenCall() { node.getFunction() = builtins_attr("open").asCfgNode() } + + override DataFlow::Node getAPathArgument() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("file")] + } + } + /** * An exec statement (only Python 2). * Se ehttps://docs.python.org/2/reference/simple_stmts.html#the-exec-statement. @@ -743,20 +757,6 @@ private module Stdlib { override DataFlow::Node getCode() { result = this } } - /** - * A call to the builtin `open` function. - * See https://docs.python.org/3/library/functions.html#open - */ - private class OpenCall extends FileSystemAccess::Range, DataFlow::CfgNode { - override CallNode node; - - OpenCall() { node.getFunction().(NameNode).getId() = "open" } - - override DataFlow::Node getAPathArgument() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("file")] - } - } - // --------------------------------------------------------------------------- // base64 // --------------------------------------------------------------------------- diff --git a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py index 103911aebdf..2bf4a6665c9 100644 --- a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py +++ b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py @@ -6,12 +6,12 @@ open(file="filepath") # $getAPathArgument="filepath" o = open -o("filepath") # $ MISSING: getAPathArgument="filepath" -o(file="filepath") # $ MISSING: getAPathArgument="filepath" +o("filepath") # $getAPathArgument="filepath" +o(file="filepath") # $getAPathArgument="filepath" -builtins.open("filepath") # $ MISSING: getAPathArgument="filepath" -builtins.open(file="filepath") # $ MISSING: getAPathArgument="filepath" +builtins.open("filepath") # $getAPathArgument="filepath" +builtins.open(file="filepath") # $getAPathArgument="filepath" io.open("filepath") # $ MISSING: getAPathArgument="filepath" From d88e5bdb3abd4809fd29bf0f7e51895d358b0964 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 24 Nov 2020 18:26:36 +0100 Subject: [PATCH 62/97] Python: Model `io.open` as FileSystemAccess --- .../src/semmle/python/frameworks/Stdlib.qll | 59 ++++++++++++++++++- .../frameworks/stdlib/FileSystemAccess.py | 4 +- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index b4813fdb031..2ca369f9221 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -736,7 +736,11 @@ private module Stdlib { private class OpenCall extends FileSystemAccess::Range, DataFlow::CfgNode { override CallNode node; - OpenCall() { node.getFunction() = builtins_attr("open").asCfgNode() } + OpenCall() { + node.getFunction() = builtins_attr("open").asCfgNode() + or + node.getFunction() = io_attr("open").asCfgNode() + } override DataFlow::Node getAPathArgument() { result.asCfgNode() in [node.getArg(0), node.getArgByName("file")] @@ -888,6 +892,59 @@ private module Stdlib { ) } } + + // --------------------------------------------------------------------------- + // io + // --------------------------------------------------------------------------- + /** Gets a reference to the `io` module. */ + private DataFlow::Node io(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::importNode("io") + or + exists(DataFlow::TypeTracker t2 | result = io(t2).track(t2, t)) + } + + /** Gets a reference to the `io` module. */ + DataFlow::Node io() { result = io(DataFlow::TypeTracker::end()) } + + /** + * Gets a reference to the attribute `attr_name` of the `io` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node io_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["open"] and + ( + t.start() and + result = DataFlow::importNode("io" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = io() + ) + or + // Due to bad performance when using normal setup with `io_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + io_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate io_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(io_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `io` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node io_attr(string attr_name) { + result = io_attr(DataFlow::TypeTracker::end(), attr_name) + } } // --------------------------------------------------------------------------- diff --git a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py index 2bf4a6665c9..109af9bd4fd 100644 --- a/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py +++ b/python/ql/test/experimental/library-tests/frameworks/stdlib/FileSystemAccess.py @@ -14,5 +14,5 @@ builtins.open("filepath") # $getAPathArgument="filepath" builtins.open(file="filepath") # $getAPathArgument="filepath" -io.open("filepath") # $ MISSING: getAPathArgument="filepath" -io.open(file="filepath") # $ MISSING: getAPathArgument="filepath" +io.open("filepath") # $getAPathArgument="filepath" +io.open(file="filepath") # $getAPathArgument="filepath" From e2c4af30316184e874809cc9fa1094af4ba4c0a2 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 25 Nov 2020 11:39:02 +0100 Subject: [PATCH 63/97] Python: Add change note for improved open modeling --- python/change-notes/2020-11-25-better-open-models.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 python/change-notes/2020-11-25-better-open-models.md diff --git a/python/change-notes/2020-11-25-better-open-models.md b/python/change-notes/2020-11-25-better-open-models.md new file mode 100644 index 00000000000..39a48ade150 --- /dev/null +++ b/python/change-notes/2020-11-25-better-open-models.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Modeling of file system access has been improved to recognize `io.open` and `builtins.open`. From 7eec988fb5f9c026ac35fab1450b51e78e4a0442 Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Wed, 25 Nov 2020 17:22:03 +0100 Subject: [PATCH 64/97] XML.qll: Remove abstract from class hierarchy. --- cpp/ql/src/semmle/code/cpp/XML.qll | 8 +++++--- csharp/ql/src/semmle/code/csharp/XML.qll | 8 +++++--- java/ql/src/semmle/code/xml/XML.qll | 8 +++++--- javascript/ql/src/semmle/javascript/XML.qll | 8 +++++--- python/ql/src/semmle/python/xml/XML.qll | 8 +++++--- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/XML.qll b/cpp/ql/src/semmle/code/cpp/XML.qll index 713903b63e6..caeb321b792 100755 --- a/cpp/ql/src/semmle/code/cpp/XML.qll +++ b/cpp/ql/src/semmle/code/cpp/XML.qll @@ -4,8 +4,10 @@ import semmle.files.FileSystem +private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; + /** An XML element that has a location. */ -abstract class XMLLocatable extends @xmllocatable { +class XMLLocatable extends @xmllocatable, TXMLLocatable { /** Gets the source location for this element. */ Location getLocation() { xmllocations(this, result) } @@ -33,7 +35,7 @@ abstract class XMLLocatable extends @xmllocatable { } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { none() } // overridden in subclasses } /** @@ -51,7 +53,7 @@ class XMLParent extends @xmlparent { * Gets a printable representation of this XML parent. * (Intended to be overridden in subclasses.) */ - abstract string getName(); + string getName() { none() } // overridden in subclasses /** Gets the file to which this XML parent belongs. */ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) } diff --git a/csharp/ql/src/semmle/code/csharp/XML.qll b/csharp/ql/src/semmle/code/csharp/XML.qll index 713903b63e6..caeb321b792 100755 --- a/csharp/ql/src/semmle/code/csharp/XML.qll +++ b/csharp/ql/src/semmle/code/csharp/XML.qll @@ -4,8 +4,10 @@ import semmle.files.FileSystem +private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; + /** An XML element that has a location. */ -abstract class XMLLocatable extends @xmllocatable { +class XMLLocatable extends @xmllocatable, TXMLLocatable { /** Gets the source location for this element. */ Location getLocation() { xmllocations(this, result) } @@ -33,7 +35,7 @@ abstract class XMLLocatable extends @xmllocatable { } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { none() } // overridden in subclasses } /** @@ -51,7 +53,7 @@ class XMLParent extends @xmlparent { * Gets a printable representation of this XML parent. * (Intended to be overridden in subclasses.) */ - abstract string getName(); + string getName() { none() } // overridden in subclasses /** Gets the file to which this XML parent belongs. */ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) } diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll index 713903b63e6..caeb321b792 100755 --- a/java/ql/src/semmle/code/xml/XML.qll +++ b/java/ql/src/semmle/code/xml/XML.qll @@ -4,8 +4,10 @@ import semmle.files.FileSystem +private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; + /** An XML element that has a location. */ -abstract class XMLLocatable extends @xmllocatable { +class XMLLocatable extends @xmllocatable, TXMLLocatable { /** Gets the source location for this element. */ Location getLocation() { xmllocations(this, result) } @@ -33,7 +35,7 @@ abstract class XMLLocatable extends @xmllocatable { } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { none() } // overridden in subclasses } /** @@ -51,7 +53,7 @@ class XMLParent extends @xmlparent { * Gets a printable representation of this XML parent. * (Intended to be overridden in subclasses.) */ - abstract string getName(); + string getName() { none() } // overridden in subclasses /** Gets the file to which this XML parent belongs. */ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) } diff --git a/javascript/ql/src/semmle/javascript/XML.qll b/javascript/ql/src/semmle/javascript/XML.qll index 713903b63e6..caeb321b792 100755 --- a/javascript/ql/src/semmle/javascript/XML.qll +++ b/javascript/ql/src/semmle/javascript/XML.qll @@ -4,8 +4,10 @@ import semmle.files.FileSystem +private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; + /** An XML element that has a location. */ -abstract class XMLLocatable extends @xmllocatable { +class XMLLocatable extends @xmllocatable, TXMLLocatable { /** Gets the source location for this element. */ Location getLocation() { xmllocations(this, result) } @@ -33,7 +35,7 @@ abstract class XMLLocatable extends @xmllocatable { } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { none() } // overridden in subclasses } /** @@ -51,7 +53,7 @@ class XMLParent extends @xmlparent { * Gets a printable representation of this XML parent. * (Intended to be overridden in subclasses.) */ - abstract string getName(); + string getName() { none() } // overridden in subclasses /** Gets the file to which this XML parent belongs. */ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) } diff --git a/python/ql/src/semmle/python/xml/XML.qll b/python/ql/src/semmle/python/xml/XML.qll index 713903b63e6..caeb321b792 100755 --- a/python/ql/src/semmle/python/xml/XML.qll +++ b/python/ql/src/semmle/python/xml/XML.qll @@ -4,8 +4,10 @@ import semmle.files.FileSystem +private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; + /** An XML element that has a location. */ -abstract class XMLLocatable extends @xmllocatable { +class XMLLocatable extends @xmllocatable, TXMLLocatable { /** Gets the source location for this element. */ Location getLocation() { xmllocations(this, result) } @@ -33,7 +35,7 @@ abstract class XMLLocatable extends @xmllocatable { } /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { none() } // overridden in subclasses } /** @@ -51,7 +53,7 @@ class XMLParent extends @xmlparent { * Gets a printable representation of this XML parent. * (Intended to be overridden in subclasses.) */ - abstract string getName(); + string getName() { none() } // overridden in subclasses /** Gets the file to which this XML parent belongs. */ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) } From 3bfb398516b1d4c5e86f936432fda5b2696e8c26 Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Wed, 25 Nov 2020 18:20:50 +0100 Subject: [PATCH 65/97] Autoformat XML.qll. --- cpp/ql/src/semmle/code/cpp/XML.qll | 3 ++- csharp/ql/src/semmle/code/csharp/XML.qll | 3 ++- java/ql/src/semmle/code/xml/XML.qll | 3 ++- javascript/ql/src/semmle/javascript/XML.qll | 3 ++- python/ql/src/semmle/python/xml/XML.qll | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/XML.qll b/cpp/ql/src/semmle/code/cpp/XML.qll index caeb321b792..5871fed0ddd 100755 --- a/cpp/ql/src/semmle/code/cpp/XML.qll +++ b/cpp/ql/src/semmle/code/cpp/XML.qll @@ -4,7 +4,8 @@ import semmle.files.FileSystem -private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; +private class TXMLLocatable = + @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; /** An XML element that has a location. */ class XMLLocatable extends @xmllocatable, TXMLLocatable { diff --git a/csharp/ql/src/semmle/code/csharp/XML.qll b/csharp/ql/src/semmle/code/csharp/XML.qll index caeb321b792..5871fed0ddd 100755 --- a/csharp/ql/src/semmle/code/csharp/XML.qll +++ b/csharp/ql/src/semmle/code/csharp/XML.qll @@ -4,7 +4,8 @@ import semmle.files.FileSystem -private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; +private class TXMLLocatable = + @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; /** An XML element that has a location. */ class XMLLocatable extends @xmllocatable, TXMLLocatable { diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll index caeb321b792..5871fed0ddd 100755 --- a/java/ql/src/semmle/code/xml/XML.qll +++ b/java/ql/src/semmle/code/xml/XML.qll @@ -4,7 +4,8 @@ import semmle.files.FileSystem -private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; +private class TXMLLocatable = + @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; /** An XML element that has a location. */ class XMLLocatable extends @xmllocatable, TXMLLocatable { diff --git a/javascript/ql/src/semmle/javascript/XML.qll b/javascript/ql/src/semmle/javascript/XML.qll index caeb321b792..5871fed0ddd 100755 --- a/javascript/ql/src/semmle/javascript/XML.qll +++ b/javascript/ql/src/semmle/javascript/XML.qll @@ -4,7 +4,8 @@ import semmle.files.FileSystem -private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; +private class TXMLLocatable = + @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; /** An XML element that has a location. */ class XMLLocatable extends @xmllocatable, TXMLLocatable { diff --git a/python/ql/src/semmle/python/xml/XML.qll b/python/ql/src/semmle/python/xml/XML.qll index caeb321b792..5871fed0ddd 100755 --- a/python/ql/src/semmle/python/xml/XML.qll +++ b/python/ql/src/semmle/python/xml/XML.qll @@ -4,7 +4,8 @@ import semmle.files.FileSystem -private class TXMLLocatable = @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; +private class TXMLLocatable = + @xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters; /** An XML element that has a location. */ class XMLLocatable extends @xmllocatable, TXMLLocatable { From 7730f5dfcf059cb4c6bf2abaa1de170049909aaf Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 25 Nov 2020 18:20:55 +0100 Subject: [PATCH 66/97] C++: Use model interfaces in SafeExternalAPIFunction and make the three previosuly-used implementation models private. --- .../src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll | 6 ++---- .../Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll | 6 ++---- .../src/semmle/code/cpp/models/implementations/Pure.qll | 8 +++++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll b/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll index 764b04d76db..c8f59bf1f7a 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll +++ b/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll @@ -13,9 +13,7 @@ abstract class SafeExternalAPIFunction extends Function { } /** The default set of "safe" external APIs. */ private class DefaultSafeExternalAPIFunction extends SafeExternalAPIFunction { DefaultSafeExternalAPIFunction() { - // implementation note: this should be based on the properties of public interfaces, rather than accessing implementation classes directly. When we've done that, the three classes referenced here should be made fully private. - this instanceof PureStrFunction or - this instanceof StrLenFunction or - this instanceof PureMemFunction + this instanceof ArrayFunction and + not this.(ArrayFunction).hasArrayOutput(_) } } diff --git a/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll b/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll index 764b04d76db..c8f59bf1f7a 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll +++ b/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll @@ -13,9 +13,7 @@ abstract class SafeExternalAPIFunction extends Function { } /** The default set of "safe" external APIs. */ private class DefaultSafeExternalAPIFunction extends SafeExternalAPIFunction { DefaultSafeExternalAPIFunction() { - // implementation note: this should be based on the properties of public interfaces, rather than accessing implementation classes directly. When we've done that, the three classes referenced here should be made fully private. - this instanceof PureStrFunction or - this instanceof StrLenFunction or - this instanceof PureMemFunction + this instanceof ArrayFunction and + not this.(ArrayFunction).hasArrayOutput(_) } } diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll index 39eb8f63c24..efc3718700e 100644 --- a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll +++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll @@ -8,7 +8,8 @@ import semmle.code.cpp.models.interfaces.SideEffect * * INTERNAL: do not use. */ -class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction, SideEffectFunction { +private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction, + SideEffectFunction { PureStrFunction() { hasGlobalOrStdName([ "atof", "atoi", "atol", "atoll", "strcasestr", "strchnul", "strchr", "strchrnul", "strstr", @@ -68,7 +69,7 @@ class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction, SideE * * INTERNAL: do not use. */ -class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFunction { +private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFunction { StrLenFunction() { hasGlobalOrStdName(["strlen", "strnlen", "wcslen"]) or @@ -123,7 +124,8 @@ private class PureFunction extends TaintFunction, SideEffectFunction { * * INTERNAL: do not use. */ -class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunction, SideEffectFunction { +private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunction, + SideEffectFunction { PureMemFunction() { hasGlobalOrStdName(["memchr", "memrchr", "rawmemchr", "memcmp", "memmem"]) } override predicate hasArrayInput(int bufParam) { From c595baf1e3d6db376c5864b87c1048dbb9e0a1a1 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 25 Nov 2020 21:07:47 +0100 Subject: [PATCH 67/97] C++: Remove INTERNAL from qldoc now that the Pure model implementations are private. --- .../code/cpp/models/implementations/Pure.qll | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll index efc3718700e..42021c48953 100644 --- a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll +++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll @@ -3,11 +3,7 @@ import semmle.code.cpp.models.interfaces.Taint import semmle.code.cpp.models.interfaces.Alias import semmle.code.cpp.models.interfaces.SideEffect -/** - * Pure string functions. - * - * INTERNAL: do not use. - */ +/** Pure string functions. */ private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction, SideEffectFunction { PureStrFunction() { @@ -64,11 +60,7 @@ private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunctio } } -/** - * String standard `strlen` function, and related functions for computing string lengths. - * - * INTERNAL: do not use. - */ +/** String standard `strlen` function, and related functions for computing string lengths. */ private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFunction { StrLenFunction() { hasGlobalOrStdName(["strlen", "strnlen", "wcslen"]) @@ -119,11 +111,7 @@ private class PureFunction extends TaintFunction, SideEffectFunction { override predicate hasOnlySpecificWriteSideEffects() { any() } } -/** - * Pure raw-memory functions. - * - * INTERNAL: do not use. - */ +/** Pure raw-memory functions. */ private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunction, SideEffectFunction { PureMemFunction() { hasGlobalOrStdName(["memchr", "memrchr", "rawmemchr", "memcmp", "memmem"]) } From 3bd6807681545686c36d2472026875a58afc9b65 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 12 Nov 2020 13:30:10 +0100 Subject: [PATCH 68/97] C#: Extract type patterns --- .../Entities/Expressions/Patterns/Pattern.cs | 3 ++ .../library-tests/csharp9/PrintAst.expected | 49 +++++++++++++++++++ .../test/library-tests/csharp9/TypePattern.cs | 17 +++++++ .../csharp9/typePattern.expected | 9 ++++ .../test/library-tests/csharp9/typePattern.ql | 4 ++ 5 files changed, 82 insertions(+) create mode 100644 csharp/ql/test/library-tests/csharp9/TypePattern.cs create mode 100644 csharp/ql/test/library-tests/csharp9/typePattern.expected create mode 100644 csharp/ql/test/library-tests/csharp9/typePattern.ql diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs index aeb70a83a5f..7fe552ec8e9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs @@ -17,6 +17,9 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions case ConstantPatternSyntax constantPattern: return Expression.Create(cx, constantPattern.Expression, parent, child); + case TypePatternSyntax typePattern: + return Expressions.TypeAccess.Create(cx, typePattern.Type, parent, child); + case DeclarationPatternSyntax declPattern: // Creates a single local variable declaration. { diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 9f0833ad572..3b2fc8380a0 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -348,3 +348,52 @@ TypeParameterNullability.cs: # 24| -1: [TypeMention] T? # 24| 1: [TypeMention] T # 24| 4: [BlockStmt] {...} +TypePattern.cs: +# 3| [Class] TypePattern +# 5| 5: [Method] M1 +# 5| -1: [TypeMention] object +#-----| 2: (Parameters) +# 5| 0: [Parameter] o1 +# 5| -1: [TypeMention] object +# 5| 1: [Parameter] o2 +# 5| -1: [TypeMention] object +# 6| 4: [BlockStmt] {...} +# 7| 0: [LocalVariableDeclStmt] ... ...; +# 7| 0: [LocalVariableDeclAndInitExpr] (Object,Object) t = ... +# 7| -1: [TypeMention] (object, object) +# 7| 0: [LocalVariableAccess] access to local variable t +# 7| 1: [TupleExpr] (..., ...) +# 7| 0: [ParameterAccess] access to parameter o1 +# 7| 1: [ParameterAccess] access to parameter o2 +# 8| 1: [IfStmt] if (...) ... +# 8| 0: [IsExpr] ... is ... +# 8| 0: [LocalVariableAccess] access to local variable t +# 8| 1: [RecursivePatternExpr] { ... } +# 8| 2: [PositionalPatternExpr] ( ... ) +# 8| 0: [TypeAccessPatternExpr] access to type Int32 +# 8| 0: [TypeMention] int +# 8| 1: [TypeAccessPatternExpr] access to type String +# 8| 0: [TypeMention] string +# 8| 1: [BlockStmt] {...} +# 9| 2: [ReturnStmt] return ...; +# 9| 0: [SwitchExpr] ... switch { ... } +# 9| -1: [ParameterAccess] access to parameter o1 +# 11| 0: [SwitchCaseExpr] ... => ... +# 11| 0: [TypeAccessPatternExpr] access to type Int32 +# 11| 0: [TypeMention] int +# 11| 2: [CastExpr] (...) ... +# 11| 1: [IntLiteral] 1 +# 12| 1: [SwitchCaseExpr] ... => ... +# 12| 0: [VariablePatternExpr] Double d +# 12| 0: [TypeMention] double +# 12| 2: [CastExpr] (...) ... +# 12| 1: [LocalVariableAccess] access to local variable d +# 13| 2: [SwitchCaseExpr] ... => ... +# 13| 0: [TypeAccessPatternExpr] access to type String +# 13| 0: [TypeMention] string +# 13| 2: [CastExpr] (...) ... +# 13| 1: [IntLiteral] 3 +# 14| 3: [SwitchCaseExpr] ... => ... +# 14| 0: [VariablePatternExpr] Object o +# 14| 0: [TypeMention] object +# 14| 2: [LocalVariableAccess] access to local variable o diff --git a/csharp/ql/test/library-tests/csharp9/TypePattern.cs b/csharp/ql/test/library-tests/csharp9/TypePattern.cs new file mode 100644 index 00000000000..100a95f9e7d --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/TypePattern.cs @@ -0,0 +1,17 @@ +using System; + +public class TypePattern +{ + object M1(object o1, object o2) + { + var t = (o1, o2); + if (t is (int, string)) {} + return o1 switch + { + int => 1, + double d => d, + System.String => 3, + System.Object o => o + }; + } +} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/typePattern.expected b/csharp/ql/test/library-tests/csharp9/typePattern.expected new file mode 100644 index 00000000000..91b8f2c1afc --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/typePattern.expected @@ -0,0 +1,9 @@ +| ParenthesizedPattern.cs:25:13:25:15 | T t | T | +| ParenthesizedPattern.cs:26:14:26:22 | Object o1 | Object | +| ParenthesizedPattern.cs:27:14:27:19 | access to type String | String | +| TypePattern.cs:8:19:8:21 | access to type Int32 | Int32 | +| TypePattern.cs:8:24:8:29 | access to type String | String | +| TypePattern.cs:11:13:11:15 | access to type Int32 | Int32 | +| TypePattern.cs:12:13:12:20 | Double d | Double | +| TypePattern.cs:13:13:13:25 | access to type String | String | +| TypePattern.cs:14:13:14:27 | Object o | Object | diff --git a/csharp/ql/test/library-tests/csharp9/typePattern.ql b/csharp/ql/test/library-tests/csharp9/typePattern.ql new file mode 100644 index 00000000000..a0a84e16826 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/typePattern.ql @@ -0,0 +1,4 @@ +import csharp + +from TypePatternExpr pattern +select pattern, pattern.getCheckedType().toString() From 5f4ad3ad7dacfd2d083cfcb69d694bb5b02b7e20 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Thu, 26 Nov 2020 10:07:44 +0100 Subject: [PATCH 69/97] C++: Fix join order in definitionHasPhiNode --- .../aliased_ssa/internal/SSAConstruction.qll | 23 ++++++++++++++----- .../internal/SSAConstruction.qll | 23 ++++++++++++++----- .../internal/SSAConstruction.qll | 23 ++++++++++++++----- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 0ce2cbed446..8e904ee6bc4 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -403,16 +403,27 @@ private import PhiInsertion * instruction for the virtual variable as a whole. */ private module PhiInsertion { + /** + * Holds if `phiBlock` is a block in the dominance frontier of a block that has a definition of the + * memory location `defLocation`. + */ + pragma[noinline] + private predicate dominanceFrontierOfDefinition( + Alias::MemoryLocation defLocation, OldBlock phiBlock + ) { + exists(OldBlock defBlock | + phiBlock = Dominance::getDominanceFrontier(defBlock) and + definitionHasDefinitionInBlock(defLocation, defBlock) + ) + } + /** * Holds if a `Phi` instruction needs to be inserted for location `defLocation` at the beginning of block `phiBlock`. */ predicate definitionHasPhiNode(Alias::MemoryLocation defLocation, OldBlock phiBlock) { - exists(OldBlock defBlock | - phiBlock = Dominance::getDominanceFrontier(defBlock) and - definitionHasDefinitionInBlock(defLocation, defBlock) and - /* We can also eliminate those nodes where the definition is not live on any incoming edge */ - definitionLiveOnEntryToBlock(defLocation, phiBlock) - ) + dominanceFrontierOfDefinition(defLocation, phiBlock) and + /* We can also eliminate those nodes where the definition is not live on any incoming edge */ + definitionLiveOnEntryToBlock(defLocation, phiBlock) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 0ce2cbed446..8e904ee6bc4 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -403,16 +403,27 @@ private import PhiInsertion * instruction for the virtual variable as a whole. */ private module PhiInsertion { + /** + * Holds if `phiBlock` is a block in the dominance frontier of a block that has a definition of the + * memory location `defLocation`. + */ + pragma[noinline] + private predicate dominanceFrontierOfDefinition( + Alias::MemoryLocation defLocation, OldBlock phiBlock + ) { + exists(OldBlock defBlock | + phiBlock = Dominance::getDominanceFrontier(defBlock) and + definitionHasDefinitionInBlock(defLocation, defBlock) + ) + } + /** * Holds if a `Phi` instruction needs to be inserted for location `defLocation` at the beginning of block `phiBlock`. */ predicate definitionHasPhiNode(Alias::MemoryLocation defLocation, OldBlock phiBlock) { - exists(OldBlock defBlock | - phiBlock = Dominance::getDominanceFrontier(defBlock) and - definitionHasDefinitionInBlock(defLocation, defBlock) and - /* We can also eliminate those nodes where the definition is not live on any incoming edge */ - definitionLiveOnEntryToBlock(defLocation, phiBlock) - ) + dominanceFrontierOfDefinition(defLocation, phiBlock) and + /* We can also eliminate those nodes where the definition is not live on any incoming edge */ + definitionLiveOnEntryToBlock(defLocation, phiBlock) } /** diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 0ce2cbed446..8e904ee6bc4 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -403,16 +403,27 @@ private import PhiInsertion * instruction for the virtual variable as a whole. */ private module PhiInsertion { + /** + * Holds if `phiBlock` is a block in the dominance frontier of a block that has a definition of the + * memory location `defLocation`. + */ + pragma[noinline] + private predicate dominanceFrontierOfDefinition( + Alias::MemoryLocation defLocation, OldBlock phiBlock + ) { + exists(OldBlock defBlock | + phiBlock = Dominance::getDominanceFrontier(defBlock) and + definitionHasDefinitionInBlock(defLocation, defBlock) + ) + } + /** * Holds if a `Phi` instruction needs to be inserted for location `defLocation` at the beginning of block `phiBlock`. */ predicate definitionHasPhiNode(Alias::MemoryLocation defLocation, OldBlock phiBlock) { - exists(OldBlock defBlock | - phiBlock = Dominance::getDominanceFrontier(defBlock) and - definitionHasDefinitionInBlock(defLocation, defBlock) and - /* We can also eliminate those nodes where the definition is not live on any incoming edge */ - definitionLiveOnEntryToBlock(defLocation, phiBlock) - ) + dominanceFrontierOfDefinition(defLocation, phiBlock) and + /* We can also eliminate those nodes where the definition is not live on any incoming edge */ + definitionLiveOnEntryToBlock(defLocation, phiBlock) } /** From ba32459adfff0ca9f6434e4f1fa67e387d0b68ed Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Thu, 26 Nov 2020 10:17:14 +0100 Subject: [PATCH 70/97] C++: Remove uses of abstract from the standard library. --- cpp/ql/src/semmle/code/cpp/Class.qll | 67 +++++++++++-------- cpp/ql/src/semmle/code/cpp/MemberFunction.qll | 15 ++++- cpp/ql/src/semmle/code/cpp/Print.qll | 2 +- cpp/ql/src/semmle/code/cpp/Type.qll | 10 ++- .../code/cpp/exprs/ComparisonOperation.qll | 4 +- cpp/ql/src/semmle/code/cpp/exprs/Expr.qll | 2 +- cpp/ql/src/semmle/code/cpp/exprs/Literal.qll | 12 +++- .../code/cpp/exprs/LogicalOperation.qll | 2 +- .../code/cpp/internal/QualifiedName.qll | 2 +- 9 files changed, 76 insertions(+), 40 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/Class.qll b/cpp/ql/src/semmle/code/cpp/Class.qll index f81f00064d7..82e444c7f38 100644 --- a/cpp/ql/src/semmle/code/cpp/Class.qll +++ b/cpp/ql/src/semmle/code/cpp/Class.qll @@ -977,7 +977,12 @@ class ClassTemplateInstantiation extends Class { * specialization - see `FullClassTemplateSpecialization` and * `PartialClassTemplateSpecialization`). */ -abstract class ClassTemplateSpecialization extends Class { +class ClassTemplateSpecialization extends Class { + ClassTemplateSpecialization() { + isFullClassTemplateSpecialization(this) or + isPartialClassTemplateSpecialization(this) + } + /** * Gets the primary template for the specialization, for example on * `S`, the result is `S`. @@ -997,6 +1002,16 @@ abstract class ClassTemplateSpecialization extends Class { override string getAPrimaryQlClass() { result = "ClassTemplateSpecialization" } } +private predicate isFullClassTemplateSpecialization(Class c) { + // This class has template arguments, but none of them involves a template parameter. + exists(c.getATemplateArgument()) and + not exists(Type ta | ta = c.getATemplateArgument() and ta.involvesTemplateParameter()) and + // This class does not have any instantiations. + not exists(c.(TemplateClass).getAnInstantiation()) and + // This class is not an instantiation of a class template. + not c instanceof ClassTemplateInstantiation +} + /** * A full specialization of a class template. For example `MyTemplateClass` * in the following code is a `FullClassTemplateSpecialization`: @@ -1013,19 +1028,31 @@ abstract class ClassTemplateSpecialization extends Class { * ``` */ class FullClassTemplateSpecialization extends ClassTemplateSpecialization { - FullClassTemplateSpecialization() { - // This class has template arguments, but none of them involves a template parameter. - exists(getATemplateArgument()) and - not exists(Type ta | ta = getATemplateArgument() and ta.involvesTemplateParameter()) and - // This class does not have any instantiations. - not exists(this.(TemplateClass).getAnInstantiation()) and - // This class is not an instantiation of a class template. - not this instanceof ClassTemplateInstantiation - } + FullClassTemplateSpecialization() { isFullClassTemplateSpecialization(this) } override string getAPrimaryQlClass() { result = "FullClassTemplateSpecialization" } } +private predicate isPartialClassTemplateSpecialization(Class c) { + /* + * (a) At least one of this class's template arguments involves a + * template parameter in some respect, for example T, T*, etc. + * + * (b) It is not the case that the n template arguments of this class + * are a set of n distinct template parameters. + * + * template class X {}; // class template + * template class X {}; // partial class template specialization + * template class X {}; // partial class template specialization + * template class Y {}; // class template + * template class Y {}; // partial class template specialization + */ + + exists(Type ta | ta = c.getATemplateArgument() and ta.involvesTemplateParameter()) and + count(TemplateParameter tp | tp = c.getATemplateArgument()) != + count(int i | exists(c.getTemplateArgument(i))) +} + /** * A partial specialization of a class template. For example `MyTemplateClass` * in the following code is a `PartialClassTemplateSpecialization`: @@ -1042,25 +1069,7 @@ class FullClassTemplateSpecialization extends ClassTemplateSpecialization { * ``` */ class PartialClassTemplateSpecialization extends ClassTemplateSpecialization { - PartialClassTemplateSpecialization() { - /* - * (a) At least one of this class's template arguments involves a - * template parameter in some respect, for example T, T*, etc. - * - * (b) It is not the case that the n template arguments of this class - * are a set of n distinct template parameters. - * - * template class X {}; // class template - * template class X {}; // partial class template specialization - * template class X {}; // partial class template specialization - * template class Y {}; // class template - * template class Y {}; // partial class template specialization - */ - - exists(Type ta | ta = getATemplateArgument() and ta.involvesTemplateParameter()) and - count(TemplateParameter tp | tp = getATemplateArgument()) != - count(int i | exists(getTemplateArgument(i))) - } + PartialClassTemplateSpecialization() { isPartialClassTemplateSpecialization(this) } override string getAPrimaryQlClass() { result = "PartialClassTemplateSpecialization" } } diff --git a/cpp/ql/src/semmle/code/cpp/MemberFunction.qll b/cpp/ql/src/semmle/code/cpp/MemberFunction.qll index 0a692f755ed..9dc1bbc7346 100644 --- a/cpp/ql/src/semmle/code/cpp/MemberFunction.qll +++ b/cpp/ql/src/semmle/code/cpp/MemberFunction.qll @@ -205,12 +205,21 @@ class Constructor extends MemberFunction { /** * A function that defines an implicit conversion. */ -abstract class ImplicitConversionFunction extends MemberFunction { +class ImplicitConversionFunction extends MemberFunction { + ImplicitConversionFunction() { + // ConversionOperator + functions(underlyingElement(this), _, 4) + or + // ConversionConstructor (deprecated) + strictcount(Parameter p | p = getAParameter() and not p.hasInitializer()) = 1 and + not hasSpecifier("explicit") + } + /** Gets the type this `ImplicitConversionFunction` takes as input. */ - abstract Type getSourceType(); + Type getSourceType() { none() } // overridden in subclasses /** Gets the type this `ImplicitConversionFunction` converts to. */ - abstract Type getDestType(); + Type getDestType() { none() } // overridden in subclasses } /** diff --git a/cpp/ql/src/semmle/code/cpp/Print.qll b/cpp/ql/src/semmle/code/cpp/Print.qll index 52ead44f4d5..4c9cc4edf27 100644 --- a/cpp/ql/src/semmle/code/cpp/Print.qll +++ b/cpp/ql/src/semmle/code/cpp/Print.qll @@ -60,7 +60,7 @@ private string getTemplateArgumentString(Declaration d, int i) { /** * A `Declaration` extended to add methods for generating strings useful only for dumps and debugging. */ -abstract private class DumpDeclaration extends Declaration { +private class DumpDeclaration extends Declaration { DumpDeclaration() { shouldPrintDeclaration(this) } /** diff --git a/cpp/ql/src/semmle/code/cpp/Type.qll b/cpp/ql/src/semmle/code/cpp/Type.qll index 5718ebf0929..e3c0719d15f 100644 --- a/cpp/ql/src/semmle/code/cpp/Type.qll +++ b/cpp/ql/src/semmle/code/cpp/Type.qll @@ -577,7 +577,15 @@ class BoolType extends IntegralType { * unsigned char e, f; * ``` */ -abstract class CharType extends IntegralType { } +class CharType extends IntegralType { + CharType() { + builtintypes(underlyingElement(this), _, 5, _, _, _) + or + builtintypes(underlyingElement(this), _, 6, _, _, _) + or + builtintypes(underlyingElement(this), _, 7, _, _, _) + } +} /** * The C/C++ `char` type (which is distinct from `signed char` and diff --git a/cpp/ql/src/semmle/code/cpp/exprs/ComparisonOperation.qll b/cpp/ql/src/semmle/code/cpp/exprs/ComparisonOperation.qll index 0c84e07f553..5ae0155fdb3 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/ComparisonOperation.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/ComparisonOperation.qll @@ -64,7 +64,7 @@ class RelationalOperation extends ComparisonOperation, @rel_op_expr { * if the overall expression evaluates to `true`; for example on * `x <= 20` this is the `20`, and on `y > 0` it is `y`. */ - abstract Expr getGreaterOperand(); + Expr getGreaterOperand() { none() } // overridden in subclasses /** * Gets the operand on the "lesser" (or "lesser-or-equal") side @@ -72,7 +72,7 @@ class RelationalOperation extends ComparisonOperation, @rel_op_expr { * if the overall expression evaluates to `true`; for example on * `x <= 20` this is `x`, and on `y > 0` it is the `0`. */ - abstract Expr getLesserOperand(); + Expr getLesserOperand() { none() } // overridden in subclasses } /** diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll index 6b23957f6ff..ba752cc7df4 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll @@ -838,7 +838,7 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr { * For example, for `new int` the result is `int`. * For `new int[5]` the result is `int[5]`. */ - abstract Type getAllocatedType(); + Type getAllocatedType() { none() } // overridden in subclasses /** * Gets the pointer `p` if this expression is of the form `new(p) T...`. diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Literal.qll b/cpp/ql/src/semmle/code/cpp/exprs/Literal.qll index 9ab944d2cc3..31790f85bfb 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Literal.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Literal.qll @@ -47,7 +47,17 @@ class LabelLiteral extends Literal { } /** A character literal or a string literal. */ -abstract class TextLiteral extends Literal { +class TextLiteral extends Literal { + TextLiteral() { + // String Literal + // Note that `AggregateLiteral`s can also have an array type, but they derive from + // @aggregateliteral rather than @literal. + this.getType() instanceof ArrayType + or + // Char literal + this.getValueText().regexpMatch("(?s)\\s*L?'.*") + } + /** Gets a hex escape sequence that appears in the character or string literal (see [lex.ccon] in the C++ Standard). */ string getAHexEscapeSequence(int occurrence, int offset) { result = getValueText().regexpFind("(? Date: Thu, 5 Nov 2020 14:21:56 +0100 Subject: [PATCH 71/97] C#: Allow attributes on local functions --- .../2020-11-18-local-function-attributable.md | 2 ++ .../ql/src/semmle/code/csharp/Attribute.qll | 3 +- csharp/ql/src/semmle/code/csharp/Callable.qll | 4 ++- csharp/ql/src/semmlecode.csharp.dbscheme | 5 +-- .../library-tests/csharp9/LocalFunction.cs | 13 +++++++- .../csharp9/LocalFunctions.expected | 3 ++ .../library-tests/csharp9/LocalFunctions.ql | 2 ++ .../library-tests/csharp9/PrintAst.expected | 32 ++++++++++++++++++- csharp/upgrades/TO_CHANGE/upgrade.properties | 2 ++ 9 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 csharp/change-notes/2020-11-18-local-function-attributable.md create mode 100644 csharp/upgrades/TO_CHANGE/upgrade.properties diff --git a/csharp/change-notes/2020-11-18-local-function-attributable.md b/csharp/change-notes/2020-11-18-local-function-attributable.md new file mode 100644 index 00000000000..38f5395f257 --- /dev/null +++ b/csharp/change-notes/2020-11-18-local-function-attributable.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The `LocalFunction` class now extends `Attributable`. This is a C# 9 change. \ No newline at end of file diff --git a/csharp/ql/src/semmle/code/csharp/Attribute.qll b/csharp/ql/src/semmle/code/csharp/Attribute.qll index c54cb567c51..c533eeed9b8 100644 --- a/csharp/ql/src/semmle/code/csharp/Attribute.qll +++ b/csharp/ql/src/semmle/code/csharp/Attribute.qll @@ -10,7 +10,8 @@ private import TypeRef * An element that can have attributes. Either an assembly (`Assembly`), a field (`Field`), * a parameter (`Parameter`), an operator (`Operator`), a method (`Method`), a constructor (`Constructor`), * a destructor (`Destructor`), a callable accessor (`CallableAccessor`), a value or reference type - * (`ValueOrRefType`), or a declaration with accessors (`DeclarationWithAccessors`). + * (`ValueOrRefType`), a declaration with accessors (`DeclarationWithAccessors`), or a local function + * (`LocalFunction`). */ class Attributable extends @attributable { /** Gets an attribute attached to this element, if any. */ diff --git a/csharp/ql/src/semmle/code/csharp/Callable.qll b/csharp/ql/src/semmle/code/csharp/Callable.qll index dfe854d4ad0..7238f4576f9 100644 --- a/csharp/ql/src/semmle/code/csharp/Callable.qll +++ b/csharp/ql/src/semmle/code/csharp/Callable.qll @@ -972,7 +972,7 @@ class ExplicitConversionOperator extends ConversionOperator { * } * ``` */ -class LocalFunction extends Callable, Modifiable, @local_function { +class LocalFunction extends Callable, Modifiable, Attributable, @local_function { override string getName() { local_functions(this, result, _, _) } override LocalFunction getUnboundDeclaration() { local_functions(this, _, _, result) } @@ -996,4 +996,6 @@ class LocalFunction extends Callable, Modifiable, @local_function { override Parameter getRawParameter(int i) { result = getParameter(i) } override string getAPrimaryQlClass() { result = "LocalFunction" } + + override string toString() { result = Callable.super.toString() } } diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme index ddd39829bb7..e0531e97fc1 100644 --- a/csharp/ql/src/semmlecode.csharp.dbscheme +++ b/csharp/ql/src/semmlecode.csharp.dbscheme @@ -227,7 +227,8 @@ tokens( @external_element = @externalMetric | @externalDefect | @externalDataElement; @attributable = @assembly | @field | @parameter | @operator | @method | @constructor - | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors; + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; /** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ @@ -986,7 +987,7 @@ case @expr.kind of | 109 = @local_function_invocation_expr | 110 = @ref_expr | 111 = @discard_expr -/* C# 8.0 */ +/* C# 8.0 */ | 112 = @range_expr | 113 = @index_expr | 114 = @switch_expr diff --git a/csharp/ql/test/library-tests/csharp9/LocalFunction.cs b/csharp/ql/test/library-tests/csharp9/LocalFunction.cs index 3b9f59f3c28..38d4c93d9a7 100644 --- a/csharp/ql/test/library-tests/csharp9/LocalFunction.cs +++ b/csharp/ql/test/library-tests/csharp9/LocalFunction.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -public class Class1 +public class LocalFunction { public async Task M1() { @@ -15,4 +15,15 @@ public class Class1 static extern void localExtern(); } + + public void M2() + { + [Obsolete] + int? dup([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] bool b, int? i) + { + return 2 * i; + } + + dup(true, 42); + } } \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected b/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected index 6b3a2339bc1..abac2009565 100644 --- a/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected +++ b/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected @@ -6,3 +6,6 @@ localFunctionModifier | LocalFunction.cs:16:9:16:41 | localExtern | extern | | LocalFunction.cs:16:9:16:41 | localExtern | private | | LocalFunction.cs:16:9:16:41 | localExtern | static | +| LocalFunction.cs:21:9:25:9 | dup | private | +localFunctionAttribute +| LocalFunction.cs:21:9:25:9 | dup | LocalFunction.cs:21:10:21:17 | [Obsolete(...)] | diff --git a/csharp/ql/test/library-tests/csharp9/LocalFunctions.ql b/csharp/ql/test/library-tests/csharp9/LocalFunctions.ql index a3ec3b6368e..2f4d709bf5a 100644 --- a/csharp/ql/test/library-tests/csharp9/LocalFunctions.ql +++ b/csharp/ql/test/library-tests/csharp9/LocalFunctions.ql @@ -5,3 +5,5 @@ query predicate noBody(LocalFunction lf) { not lf.hasBody() } query predicate localFunctionModifier(LocalFunction lf, string modifier) { lf.hasModifier(modifier) } + +query predicate localFunctionAttribute(LocalFunction lf, Attribute a) { a = lf.getAnAttribute() } diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 3b2fc8380a0..2961f3d6a32 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -48,7 +48,7 @@ Discard.cs: # 10| 0: [ReturnStmt] return ...; # 10| 0: [IntLiteral] 0 LocalFunction.cs: -# 4| [Class] Class1 +# 4| [Class] LocalFunction # 6| 5: [Method] M1 # 6| -1: [TypeMention] Task # 7| 4: [BlockStmt] {...} @@ -77,6 +77,36 @@ LocalFunction.cs: # 14| 0: [IntLiteral] 2 # 16| 3: [LocalFunctionStmt] localExtern(...) # 16| 0: [LocalFunction] localExtern +# 19| 6: [Method] M2 +# 19| -1: [TypeMention] Void +# 20| 4: [BlockStmt] {...} +# 21| 0: [LocalFunctionStmt] dup(...) +# 21| 0: [LocalFunction] dup +#-----| 0: (Attributes) +# 21| 1: [Attribute] [Obsolete(...)] +# 21| 0: [TypeMention] ObsoleteAttribute +#-----| 2: (Parameters) +# 22| 0: [Parameter] b +# 22| -1: [TypeMention] bool +#-----| 0: (Attributes) +# 22| 1: [Attribute] [NotNullWhen(...)] +# 22| -1: [TypeMention] NotNullWhenAttribute +# 22| 0: [BoolLiteral] true +# 22| 1: [Parameter] i +# 22| -1: [TypeMention] int? +# 22| 1: [TypeMention] int +# 23| 4: [BlockStmt] {...} +# 24| 0: [ReturnStmt] return ...; +# 24| 0: [MulExpr] ... * ... +# 24| 0: [CastExpr] (...) ... +# 24| 1: [IntLiteral] 2 +# 24| 1: [ParameterAccess] access to parameter i +# 27| 1: [ExprStmt] ...; +# 27| 0: [LocalFunctionCall] call to local function dup +# 27| -1: [LocalFunctionAccess] access to local function dup +# 27| 0: [BoolLiteral] true +# 27| 1: [CastExpr] (...) ... +# 27| 1: [IntLiteral] 42 NativeInt.cs: # 3| [Class] NativeInt # 5| 5: [Method] M1 diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/TO_CHANGE/upgrade.properties new file mode 100644 index 00000000000..e1ae21db9b6 --- /dev/null +++ b/csharp/upgrades/TO_CHANGE/upgrade.properties @@ -0,0 +1,2 @@ +description: Added 'local_function' to 'attributable'. +compatibility: backwards From 18a757445d5bca9ee0c26e994ef0166683a3797f Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 26 Nov 2020 10:35:45 +0100 Subject: [PATCH 72/97] Add DB upgrade folder --- .../old.dbscheme | 1889 ++++++++++++++++ .../semmlecode.csharp.dbscheme | 1890 +++++++++++++++++ .../upgrade.properties | 0 3 files changed, 3779 insertions(+) create mode 100644 csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/old.dbscheme create mode 100644 csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/semmlecode.csharp.dbscheme rename csharp/upgrades/{TO_CHANGE => ddd39829bb71811b1fcb6559c0efe34f3fb6aa03}/upgrade.properties (100%) diff --git a/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/old.dbscheme b/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/old.dbscheme new file mode 100644 index 00000000000..ddd39829bb7 --- /dev/null +++ b/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/old.dbscheme @@ -0,0 +1,1889 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/semmlecode.csharp.dbscheme b/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/semmlecode.csharp.dbscheme new file mode 100644 index 00000000000..e0531e97fc1 --- /dev/null +++ b/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/semmlecode.csharp.dbscheme @@ -0,0 +1,1890 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/upgrade.properties similarity index 100% rename from csharp/upgrades/TO_CHANGE/upgrade.properties rename to csharp/upgrades/ddd39829bb71811b1fcb6559c0efe34f3fb6aa03/upgrade.properties From 5d80417854a9b78e88ed87592186c177a85a7be7 Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Thu, 26 Nov 2020 10:39:17 +0100 Subject: [PATCH 73/97] Update cpp/ql/src/semmle/code/cpp/Type.qll Co-authored-by: Mathias Vorreiter Pedersen --- cpp/ql/src/semmle/code/cpp/Type.qll | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/Type.qll b/cpp/ql/src/semmle/code/cpp/Type.qll index e3c0719d15f..cb9c94edb44 100644 --- a/cpp/ql/src/semmle/code/cpp/Type.qll +++ b/cpp/ql/src/semmle/code/cpp/Type.qll @@ -579,11 +579,7 @@ class BoolType extends IntegralType { */ class CharType extends IntegralType { CharType() { - builtintypes(underlyingElement(this), _, 5, _, _, _) - or - builtintypes(underlyingElement(this), _, 6, _, _, _) - or - builtintypes(underlyingElement(this), _, 7, _, _, _) + builtintypes(underlyingElement(this), _, [5, 6, 7], _, _, _) } } From b02ac7f5232225f5bab91dce8690675dc33bebc6 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Thu, 26 Nov 2020 11:02:47 +0100 Subject: [PATCH 74/97] C++: Use SideEffectFunction (instead of ArrayFunction) to define DefaultSafeExternalAPIFunction. --- .../Security/CWE/CWE-020/SafeExternalAPIFunction.qll | 11 ++++++++--- .../CWE/CWE-020/ir/SafeExternalAPIFunction.qll | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll b/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll index c8f59bf1f7a..5eb0b23d914 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll +++ b/cpp/ql/src/Security/CWE/CWE-020/SafeExternalAPIFunction.qll @@ -3,7 +3,7 @@ */ private import cpp -private import semmle.code.cpp.models.implementations.Pure +private import semmle.code.cpp.models.interfaces.SideEffect /** * A `Function` that is considered a "safe" external API from a security perspective. @@ -13,7 +13,12 @@ abstract class SafeExternalAPIFunction extends Function { } /** The default set of "safe" external APIs. */ private class DefaultSafeExternalAPIFunction extends SafeExternalAPIFunction { DefaultSafeExternalAPIFunction() { - this instanceof ArrayFunction and - not this.(ArrayFunction).hasArrayOutput(_) + // If a function does not write to any of its arguments, we consider it safe to + // pass untrusted data to it. This means that string functions such as `strcmp` + // and `strlen`, as well as memory functions such as `memcmp`, are considered safe. + exists(SideEffectFunction model | model = this | + model.hasOnlySpecificWriteSideEffects() and + not model.hasSpecificWriteSideEffect(_, _, _) + ) } } diff --git a/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll b/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll index c8f59bf1f7a..5eb0b23d914 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll +++ b/cpp/ql/src/Security/CWE/CWE-020/ir/SafeExternalAPIFunction.qll @@ -3,7 +3,7 @@ */ private import cpp -private import semmle.code.cpp.models.implementations.Pure +private import semmle.code.cpp.models.interfaces.SideEffect /** * A `Function` that is considered a "safe" external API from a security perspective. @@ -13,7 +13,12 @@ abstract class SafeExternalAPIFunction extends Function { } /** The default set of "safe" external APIs. */ private class DefaultSafeExternalAPIFunction extends SafeExternalAPIFunction { DefaultSafeExternalAPIFunction() { - this instanceof ArrayFunction and - not this.(ArrayFunction).hasArrayOutput(_) + // If a function does not write to any of its arguments, we consider it safe to + // pass untrusted data to it. This means that string functions such as `strcmp` + // and `strlen`, as well as memory functions such as `memcmp`, are considered safe. + exists(SideEffectFunction model | model = this | + model.hasOnlySpecificWriteSideEffects() and + not model.hasSpecificWriteSideEffect(_, _, _) + ) } } From f6c3c2bdcce73a87fe3277f7fe3cb4f935043ab6 Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Thu, 26 Nov 2020 11:45:49 +0100 Subject: [PATCH 75/97] C++: Auto-format Type.qll. --- cpp/ql/src/semmle/code/cpp/Type.qll | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/Type.qll b/cpp/ql/src/semmle/code/cpp/Type.qll index cb9c94edb44..81b28cca4c9 100644 --- a/cpp/ql/src/semmle/code/cpp/Type.qll +++ b/cpp/ql/src/semmle/code/cpp/Type.qll @@ -578,9 +578,7 @@ class BoolType extends IntegralType { * ``` */ class CharType extends IntegralType { - CharType() { - builtintypes(underlyingElement(this), _, [5, 6, 7], _, _, _) - } + CharType() { builtintypes(underlyingElement(this), _, [5, 6, 7], _, _, _) } } /** From cb91dc1308760e2676c6e020d74d7dc219b6f559 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 26 Nov 2020 15:13:57 +0100 Subject: [PATCH 76/97] C#: Rank `StandardStmt::getChildElement()` --- .../internal/ControlFlowGraphImpl.qll | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll index 1a67b061425..6952c0406b6 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -940,7 +940,7 @@ module Statements { not this instanceof JumpStmt } - final override ControlFlowTree getChildElement(int i) { + private ControlFlowTree getChildElement0(int i) { not this instanceof GeneralCatchClause and not this instanceof FixedStmt and not this instanceof UsingBlockStmt and @@ -959,25 +959,24 @@ module Statements { or this = any(UsingBlockStmt us | - if exists(us.getExpr()) - then ( - result = us.getExpr() and - i = 0 - or - result = us.getBody() and - i = 1 - ) else ( - result = us.getVariableDeclExpr(i) - or - result = us.getBody() and - i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 - ) + result = us.getExpr() and + i = 0 + or + result = us.getVariableDeclExpr(i) + or + result = us.getBody() and + i = max([1, count(us.getVariableDeclExpr(_))]) ) or result = this.(DefaultCase).getStmt() and i = 0 } + final override ControlFlowTree getChildElement(int i) { + result = + rank[i + 1](ControlFlowElement cfe, int j | cfe = this.getChildElement0(j) | cfe order by j) + } + final override predicate last(ControlFlowElement last, Completion c) { last(this.getLastChild(), last, c) or From 55d47a70f4911d0eb405482b68aaf91fd1f44b25 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 5 Nov 2020 14:12:45 +0100 Subject: [PATCH 77/97] C#: Extract modifiers for lambdas (async, static) --- .../2020-11-18-lambda-modifiers.md | 2 + .../Entities/Expressions/Lambda.cs | 12 ++++ .../Entities/Modifier.cs | 22 +++++-- .../src/semmle/code/csharp/exprs/Creation.qll | 2 +- csharp/ql/src/semmlecode.csharp.dbscheme | 2 +- .../library-tests/csharp9/Discard.expected | 3 + .../library-tests/csharp9/LambdaModifier.cs | 17 ++++++ .../csharp9/LambdaModifier.expected | 3 + .../library-tests/csharp9/LambdaModifier.ql | 5 ++ .../csharp9/LocalFunctions.expected | 1 + .../library-tests/csharp9/PrintAst.expected | 60 +++++++++++++++++++ csharp/upgrades/TO_CHANGE/upgrade.properties | 2 + 12 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 csharp/change-notes/2020-11-18-lambda-modifiers.md create mode 100644 csharp/ql/test/library-tests/csharp9/LambdaModifier.cs create mode 100644 csharp/ql/test/library-tests/csharp9/LambdaModifier.expected create mode 100644 csharp/ql/test/library-tests/csharp9/LambdaModifier.ql create mode 100644 csharp/upgrades/TO_CHANGE/upgrade.properties diff --git a/csharp/change-notes/2020-11-18-lambda-modifiers.md b/csharp/change-notes/2020-11-18-lambda-modifiers.md new file mode 100644 index 00000000000..7898d197ed6 --- /dev/null +++ b/csharp/change-notes/2020-11-18-lambda-modifiers.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The `AnonymousFunctionExpr` class now extends `Modifiable`. This change allows storing the `async` modifier for lambdas, which was missing before, and the `static` modifier, which was added in C# 9. \ No newline at end of file diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs index 28e2b3b7601..02bd23be0a7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs @@ -24,6 +24,18 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions private Lambda(ExpressionNodeInfo info, CSharpSyntaxNode body, IEnumerable @params) : base(info) { + var symbol = cx.GetModel(info.Node).GetSymbolInfo(info.Node).Symbol as IMethodSymbol; + + if (symbol is object) + { + Modifier.ExtractStaticModifier(cx, info.Context.TrapWriter.Writer, this, symbol); + Modifier.ExtractAsyncModifier(cx, info.Context.TrapWriter.Writer, this, symbol); + } + else + { + cx.ModelError(info.Node, "Unknown declared symbol"); + } + // No need to use `Populate` as the population happens later cx.PopulateLater(() => { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs index 4b2725b89f3..ef7e67e6926 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs @@ -86,10 +86,7 @@ namespace Semmle.Extraction.CSharp.Entities if (symbol.IsSealed) HasModifier(cx, trapFile, key, "sealed"); - var fromSource = symbol.DeclaringSyntaxReferences.Length > 0; - - if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource)) - HasModifier(cx, trapFile, key, "static"); + ExtractStaticModifier(cx, trapFile, key, symbol); if (symbol.IsVirtual) HasModifier(cx, trapFile, key, "virtual"); @@ -104,8 +101,7 @@ namespace Semmle.Extraction.CSharp.Entities if (symbol.IsOverride) HasModifier(cx, trapFile, key, "override"); - if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync) - HasModifier(cx, trapFile, key, "async"); + ExtractAsyncModifier(cx, trapFile, key, symbol); if (symbol.IsExtern) HasModifier(cx, trapFile, key, "extern"); @@ -129,6 +125,20 @@ namespace Semmle.Extraction.CSharp.Entities } } + public static void ExtractAsyncModifier(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol) + { + if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync) + HasModifier(cx, trapFile, key, "async"); + } + + public static void ExtractStaticModifier(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol) + { + var fromSource = symbol.DeclaringSyntaxReferences.Length > 0; + + if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource)) + HasModifier(cx, trapFile, key, "static"); + } + public static Modifier Create(Context cx, string modifier) { return ModifierFactory.Instance.CreateEntity(cx, (typeof(Modifier), modifier), modifier); diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Creation.qll b/csharp/ql/src/semmle/code/csharp/exprs/Creation.qll index 77e9df165fb..bde4f4a319d 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/Creation.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/Creation.qll @@ -407,7 +407,7 @@ class Stackalloc extends ArrayCreation { * An anonymous function. Either a lambda expression (`LambdaExpr`) or an * anonymous method expression (`AnonymousMethodExpr`). */ -class AnonymousFunctionExpr extends Expr, Callable, @anonymous_function_expr { +class AnonymousFunctionExpr extends Expr, Callable, Modifiable, @anonymous_function_expr { override string getName() { result = "" } override Type getReturnType() { diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme index e0531e97fc1..eedef9359e1 100644 --- a/csharp/ql/src/semmlecode.csharp.dbscheme +++ b/csharp/ql/src/semmlecode.csharp.dbscheme @@ -541,7 +541,7 @@ specific_type_parameter_nullability( @modifiable = @modifiable_direct | @event_accessor; -@modifiable_direct = @member | @accessor | @local_function; +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; modifiers( unique int id: @modifier, diff --git a/csharp/ql/test/library-tests/csharp9/Discard.expected b/csharp/ql/test/library-tests/csharp9/Discard.expected index 84044eb8a52..8d6d8e64165 100644 --- a/csharp/ql/test/library-tests/csharp9/Discard.expected +++ b/csharp/ql/test/library-tests/csharp9/Discard.expected @@ -6,3 +6,6 @@ | Discard.cs:9:13:9:32 | (...) => ... | Discard.cs:9:25:9:25 | _`1 | | Discard.cs:10:13:10:49 | delegate(...) { ... } | Discard.cs:10:27:10:27 | _ | | Discard.cs:10:13:10:49 | delegate(...) { ... } | Discard.cs:10:34:10:34 | _`1 | +| LambdaModifier.cs:11:11:11:27 | (...) => ... | LambdaModifier.cs:11:18:11:18 | x | +| LambdaModifier.cs:12:11:12:20 | (...) => ... | LambdaModifier.cs:12:11:12:11 | x | +| LambdaModifier.cs:13:11:13:51 | delegate(...) { ... } | LambdaModifier.cs:13:32:13:32 | x | diff --git a/csharp/ql/test/library-tests/csharp9/LambdaModifier.cs b/csharp/ql/test/library-tests/csharp9/LambdaModifier.cs new file mode 100644 index 00000000000..2895b0beed1 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/LambdaModifier.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +public class Class1 +{ + public async Task M1() + { + void m(Func f) { } + + const int z = 10; + m(static x => x + z); + m(x => x + z); + m(static delegate (int x) { return x + z; }); + + await Task.Run(async () => { await Task.CompletedTask; }); + } +} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected b/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected new file mode 100644 index 00000000000..33ce0da7f3f --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected @@ -0,0 +1,3 @@ +| LambdaModifier.cs:11:11:11:27 | (...) => ... | static | +| LambdaModifier.cs:13:11:13:51 | delegate(...) { ... } | static | +| LambdaModifier.cs:15:24:15:64 | (...) => ... | async | diff --git a/csharp/ql/test/library-tests/csharp9/LambdaModifier.ql b/csharp/ql/test/library-tests/csharp9/LambdaModifier.ql new file mode 100644 index 00000000000..02adec30e5e --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/LambdaModifier.ql @@ -0,0 +1,5 @@ +import csharp + +from AnonymousFunctionExpr anon, string modifier +where anon.hasModifier(modifier) +select anon, modifier diff --git a/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected b/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected index abac2009565..fd746b2a58b 100644 --- a/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected +++ b/csharp/ql/test/library-tests/csharp9/LocalFunctions.expected @@ -1,6 +1,7 @@ noBody | LocalFunction.cs:16:9:16:41 | localExtern | localFunctionModifier +| LambdaModifier.cs:8:9:8:36 | m | private | | LocalFunction.cs:9:9:12:9 | mul | async | | LocalFunction.cs:9:9:12:9 | mul | private | | LocalFunction.cs:16:9:16:41 | localExtern | extern | diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 2961f3d6a32..cd5b3a656a7 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -47,6 +47,66 @@ Discard.cs: # 10| 4: [BlockStmt] {...} # 10| 0: [ReturnStmt] return ...; # 10| 0: [IntLiteral] 0 +LambdaModifier.cs: +# 4| [Class] Class1 +# 6| 5: [Method] M1 +# 6| -1: [TypeMention] Task +# 7| 4: [BlockStmt] {...} +# 8| 0: [LocalFunctionStmt] m(...) +# 8| 0: [LocalFunction] m +#-----| 2: (Parameters) +# 8| 0: [Parameter] f +# 8| -1: [TypeMention] Func +# 8| 1: [TypeMention] int +# 8| 2: [TypeMention] int +# 8| 4: [BlockStmt] {...} +# 10| 1: [LocalConstantDeclStmt] const ... ...; +# 10| 0: [LocalVariableDeclAndInitExpr] Int32 z = ... +# 10| -1: [TypeMention] int +# 10| 0: [LocalVariableAccess] access to local variable z +# 10| 1: [IntLiteral] 10 +# 11| 2: [ExprStmt] ...; +# 11| 0: [LocalFunctionCall] call to local function m +# 11| -1: [LocalFunctionAccess] access to local function m +# 11| 0: [LambdaExpr] (...) => ... +#-----| 2: (Parameters) +# 11| 0: [Parameter] x +# 11| 4: [AddExpr] ... + ... +# 11| 0: [ParameterAccess] access to parameter x +# 11| 1: [LocalVariableAccess] access to local variable z +# 12| 3: [ExprStmt] ...; +# 12| 0: [LocalFunctionCall] call to local function m +# 12| -1: [LocalFunctionAccess] access to local function m +# 12| 0: [LambdaExpr] (...) => ... +#-----| 2: (Parameters) +# 12| 0: [Parameter] x +# 12| 4: [AddExpr] ... + ... +# 12| 0: [ParameterAccess] access to parameter x +# 12| 1: [LocalVariableAccess] access to local variable z +# 13| 4: [ExprStmt] ...; +# 13| 0: [LocalFunctionCall] call to local function m +# 13| -1: [LocalFunctionAccess] access to local function m +# 13| 0: [AnonymousMethodExpr] delegate(...) { ... } +#-----| 2: (Parameters) +# 13| 0: [Parameter] x +# 13| -1: [TypeMention] int +# 13| 4: [BlockStmt] {...} +# 13| 0: [ReturnStmt] return ...; +# 13| 0: [AddExpr] ... + ... +# 13| 0: [ParameterAccess] access to parameter x +# 13| 1: [LocalVariableAccess] access to local variable z +# 15| 5: [ExprStmt] ...; +# 15| 0: [AwaitExpr] await ... +# 15| 0: [MethodCall] call to method Run +# 15| -1: [TypeAccess] access to type Task +# 15| 0: [TypeMention] Task +# 15| 0: [LambdaExpr] (...) => ... +# 15| 4: [BlockStmt] {...} +# 15| 0: [ExprStmt] ...; +# 15| 0: [AwaitExpr] await ... +# 15| 0: [PropertyCall] access to property CompletedTask +# 15| -1: [TypeAccess] access to type Task +# 15| 0: [TypeMention] Task LocalFunction.cs: # 4| [Class] LocalFunction # 6| 5: [Method] M1 diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/TO_CHANGE/upgrade.properties new file mode 100644 index 00000000000..9c3159c776f --- /dev/null +++ b/csharp/upgrades/TO_CHANGE/upgrade.properties @@ -0,0 +1,2 @@ +description: Added 'anonymous_function_expr' to 'modifiable_direct'. +compatibility: backwards From 47ca4b0f3b573d6a1412ea5b961f07d30c23a276 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Wed, 25 Nov 2020 14:58:58 +0100 Subject: [PATCH 78/97] Address review comments --- .../Entities/Expressions/Lambda.cs | 7 ++---- .../Entities/Modifier.cs | 22 +++++-------------- .../csharp9/LambdaModifier.expected | 8 +++++++ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs index 02bd23be0a7..40f200ef5ef 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Lambda.cs @@ -24,12 +24,9 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions private Lambda(ExpressionNodeInfo info, CSharpSyntaxNode body, IEnumerable @params) : base(info) { - var symbol = cx.GetModel(info.Node).GetSymbolInfo(info.Node).Symbol as IMethodSymbol; - - if (symbol is object) + if (cx.GetModel(info.Node).GetSymbolInfo(info.Node).Symbol is IMethodSymbol symbol) { - Modifier.ExtractStaticModifier(cx, info.Context.TrapWriter.Writer, this, symbol); - Modifier.ExtractAsyncModifier(cx, info.Context.TrapWriter.Writer, this, symbol); + Modifier.ExtractModifiers(cx, info.Context.TrapWriter.Writer, this, symbol); } else { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs index ef7e67e6926..4b2725b89f3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs @@ -86,7 +86,10 @@ namespace Semmle.Extraction.CSharp.Entities if (symbol.IsSealed) HasModifier(cx, trapFile, key, "sealed"); - ExtractStaticModifier(cx, trapFile, key, symbol); + var fromSource = symbol.DeclaringSyntaxReferences.Length > 0; + + if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource)) + HasModifier(cx, trapFile, key, "static"); if (symbol.IsVirtual) HasModifier(cx, trapFile, key, "virtual"); @@ -101,7 +104,8 @@ namespace Semmle.Extraction.CSharp.Entities if (symbol.IsOverride) HasModifier(cx, trapFile, key, "override"); - ExtractAsyncModifier(cx, trapFile, key, symbol); + if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync) + HasModifier(cx, trapFile, key, "async"); if (symbol.IsExtern) HasModifier(cx, trapFile, key, "extern"); @@ -125,20 +129,6 @@ namespace Semmle.Extraction.CSharp.Entities } } - public static void ExtractAsyncModifier(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol) - { - if (symbol.Kind == SymbolKind.Method && ((IMethodSymbol)symbol).IsAsync) - HasModifier(cx, trapFile, key, "async"); - } - - public static void ExtractStaticModifier(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol) - { - var fromSource = symbol.DeclaringSyntaxReferences.Length > 0; - - if (symbol.IsStatic && !(symbol.Kind == SymbolKind.Field && ((IFieldSymbol)symbol).IsConst && !fromSource)) - HasModifier(cx, trapFile, key, "static"); - } - public static Modifier Create(Context cx, string modifier) { return ModifierFactory.Instance.CreateEntity(cx, (typeof(Modifier), modifier), modifier); diff --git a/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected b/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected index 33ce0da7f3f..16bb33a83a4 100644 --- a/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected +++ b/csharp/ql/test/library-tests/csharp9/LambdaModifier.expected @@ -1,3 +1,11 @@ +| Discard.cs:7:33:7:52 | (...) => ... | private | +| Discard.cs:8:13:8:24 | (...) => ... | private | +| Discard.cs:9:13:9:32 | (...) => ... | private | +| Discard.cs:10:13:10:49 | delegate(...) { ... } | private | +| LambdaModifier.cs:11:11:11:27 | (...) => ... | private | | LambdaModifier.cs:11:11:11:27 | (...) => ... | static | +| LambdaModifier.cs:12:11:12:20 | (...) => ... | private | +| LambdaModifier.cs:13:11:13:51 | delegate(...) { ... } | private | | LambdaModifier.cs:13:11:13:51 | delegate(...) { ... } | static | | LambdaModifier.cs:15:24:15:64 | (...) => ... | async | +| LambdaModifier.cs:15:24:15:64 | (...) => ... | private | From 864fce43bdd87201ae7a49050d9f32267f6475d0 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 26 Nov 2020 16:14:38 +0100 Subject: [PATCH 79/97] C#: Add upgrade folder --- .../old.dbscheme | 1890 +++++++++++++++++ .../semmlecode.csharp.dbscheme | 1890 +++++++++++++++++ .../upgrade.properties | 0 3 files changed, 3780 insertions(+) create mode 100644 csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/old.dbscheme create mode 100644 csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/semmlecode.csharp.dbscheme rename csharp/upgrades/{TO_CHANGE => e0531e97fc1251265b06a94b3047a1b6fa484dcc}/upgrade.properties (100%) diff --git a/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/old.dbscheme b/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/old.dbscheme new file mode 100644 index 00000000000..e0531e97fc1 --- /dev/null +++ b/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/old.dbscheme @@ -0,0 +1,1890 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/semmlecode.csharp.dbscheme b/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/semmlecode.csharp.dbscheme new file mode 100644 index 00000000000..eedef9359e1 --- /dev/null +++ b/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/semmlecode.csharp.dbscheme @@ -0,0 +1,1890 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/upgrade.properties similarity index 100% rename from csharp/upgrades/TO_CHANGE/upgrade.properties rename to csharp/upgrades/e0531e97fc1251265b06a94b3047a1b6fa484dcc/upgrade.properties From e2419e8fed86a9b16ebe060d6fd55d522a1f26e9 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Wed, 15 Jan 2020 17:04:10 -0500 Subject: [PATCH 80/97] Java: add SMAP relations to dbscheme --- java/ql/src/config/semmlecode.dbscheme | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/java/ql/src/config/semmlecode.dbscheme b/java/ql/src/config/semmlecode.dbscheme index 2a682863863..60a4ba1a475 100755 --- a/java/ql/src/config/semmlecode.dbscheme +++ b/java/ql/src/config/semmlecode.dbscheme @@ -171,6 +171,34 @@ tokens( int endColumn : int ref ); +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + /* * Locations and files */ From edb41655b443662b8134099af6c4968938a617ad Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Thu, 23 Jan 2020 22:35:32 -0500 Subject: [PATCH 81/97] Java: incorporate SMAP locations into `Top.hasLocationInfo` --- java/ql/src/semmle/code/Location.qll | 13 ++++++++++ java/ql/src/semmle/code/SMAP.qll | 36 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 java/ql/src/semmle/code/SMAP.qll diff --git a/java/ql/src/semmle/code/Location.qll b/java/ql/src/semmle/code/Location.qll index 8bfd54ae79d..ea78436d4f4 100755 --- a/java/ql/src/semmle/code/Location.qll +++ b/java/ql/src/semmle/code/Location.qll @@ -6,6 +6,7 @@ import FileSystem import semmle.code.java.Element +import semmle.code.SMAP /** Holds if element `e` has name `name`. */ predicate hasName(Element e, string name) { @@ -61,6 +62,18 @@ class Top extends @top { */ predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + hasLocationInfoAux(filepath, startline, startcolumn, endline, endcolumn) + or + exists(string outFilepath, int outStartline, int outEndline | + hasLocationInfoAux(outFilepath, outStartline, _, outEndline, _) and + hasSmapLocationInfo(filepath, startline, startcolumn, endline, endcolumn, outFilepath, + outStartline, outEndline) + ) + } + + predicate hasLocationInfoAux( + string filepath, int startline, int startcolumn, int endline, int endcolumn ) { exists(File f, Location l | fixedHasLocation(this, l, f) | locations_default(l, f, startline, startcolumn, endline, endcolumn) and diff --git a/java/ql/src/semmle/code/SMAP.qll b/java/ql/src/semmle/code/SMAP.qll new file mode 100644 index 00000000000..c39f1a79797 --- /dev/null +++ b/java/ql/src/semmle/code/SMAP.qll @@ -0,0 +1,36 @@ +import java + +predicate smap(File inputFile, int inLine, File outputFile, int outLineStart, int outLineEnd) { + exists( + string defaultStratum, int inputFileNum, int inStart, int inCount, int outStart, int outIncr, + int n + | + smap_header(outputFile, _, defaultStratum) and + smap_files(outputFile, defaultStratum, inputFileNum, _, inputFile) and + smap_lines(outputFile, defaultStratum, inputFileNum, inStart, inCount, outStart, outIncr) and + inLine in [inStart .. inStart + inCount - 1] and + outLineStart = outStart + n * outIncr and + outLineEnd = (n + 1) * outIncr - 1 + outStart and + n = inLine - inStart + ) +} + +predicate smap(File inputFile, int inLine, File outputFile, int outLine) { + exists(int outLineStart, int outLineEnd | + smap(inputFile, inLine, outputFile, outLineStart, outLineEnd) and + outLine in [outLineStart .. outLineEnd] + ) +} + +predicate hasSmapLocationInfo( + string inputPath, int isl, int isc, int iel, int iec, string outputPath, int osl, int oel +) { + exists(File inputFile, File outputFile | + inputPath = inputFile.getAbsolutePath() and + outputPath = outputFile.getAbsolutePath() and + smap(inputFile, isl, outputFile, osl) and + smap(inputFile, iel - 1, outputFile, oel) and + isc = 1 and + iec = 0 + ) +} From f9e78085ac4b757920f3cfd4764f3ec13c65883e Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Fri, 24 Jan 2020 14:38:16 -0500 Subject: [PATCH 82/97] Java: add dbscheme stats for SMAP relations --- java/ql/src/config/semmlecode.dbscheme.stats | 2027 ++++++++++++++++++ 1 file changed, 2027 insertions(+) diff --git a/java/ql/src/config/semmlecode.dbscheme.stats b/java/ql/src/config/semmlecode.dbscheme.stats index ae0f5e3b34f..6b9f21c5f71 100644 --- a/java/ql/src/config/semmlecode.dbscheme.stats +++ b/java/ql/src/config/semmlecode.dbscheme.stats @@ -2483,6 +2483,2033 @@ +smap_header +117 + + +outputFileId +117 + + +outputFilename +78 + + +defaultStratum +1 + + + + +outputFileId +outputFilename + + +12 + + +1 +2 +117 + + + + + + +outputFileId +defaultStratum + + +12 + + +1 +2 +117 + + + + + + +outputFilename +outputFileId + + +12 + + +1 +2 +41 + + +2 +3 +36 + + +4 +5 +1 + + + + + + +outputFilename +defaultStratum + + +12 + + +1 +2 +78 + + + + + + +defaultStratum +outputFileId + + +12 + + +117 +118 +1 + + + + + + +defaultStratum +outputFilename + + +12 + + +78 +79 +1 + + + + + + + + +smap_files +117 + + +outputFileId +117 + + +stratum +1 + + +inputFileNum +1 + + +inputFileName +78 + + +inputFileId +117 + + + + +outputFileId +stratum + + +12 + + +1 +2 +117 + + + + + + +outputFileId +inputFileNum + + +12 + + +1 +2 +117 + + + + + + +outputFileId +inputFileName + + +12 + + +1 +2 +117 + + + + + + +outputFileId +inputFileId + + +12 + + +1 +2 +117 + + + + + + +stratum +outputFileId + + +12 + + +117 +118 +1 + + + + + + +stratum +inputFileNum + + +12 + + +1 +2 +1 + + + + + + +stratum +inputFileName + + +12 + + +78 +79 +1 + + + + + + +stratum +inputFileId + + +12 + + +117 +118 +1 + + + + + + +inputFileNum +outputFileId + + +12 + + +117 +118 +1 + + + + + + +inputFileNum +stratum + + +12 + + +1 +2 +1 + + + + + + +inputFileNum +inputFileName + + +12 + + +78 +79 +1 + + + + + + +inputFileNum +inputFileId + + +12 + + +117 +118 +1 + + + + + + +inputFileName +outputFileId + + +12 + + +1 +2 +41 + + +2 +3 +36 + + +4 +5 +1 + + + + + + +inputFileName +stratum + + +12 + + +1 +2 +78 + + + + + + +inputFileName +inputFileNum + + +12 + + +1 +2 +78 + + + + + + +inputFileName +inputFileId + + +12 + + +1 +2 +41 + + +2 +3 +36 + + +4 +5 +1 + + + + + + +inputFileId +outputFileId + + +12 + + +1 +2 +117 + + + + + + +inputFileId +stratum + + +12 + + +1 +2 +117 + + + + + + +inputFileId +inputFileNum + + +12 + + +1 +2 +117 + + + + + + +inputFileId +inputFileName + + +12 + + +1 +2 +117 + + + + + + + + +smap_lines +4809 + + +outputFileId +116 + + +stratum +1 + + +inputFileNum +1 + + +inputStartLine +354 + + +inputLineCount +57 + + +outputStartLine +1873 + + +outputLineIncrement +19 + + + + +outputFileId +stratum + + +12 + + +1 +2 +116 + + + + + + +outputFileId +inputFileNum + + +12 + + +1 +2 +116 + + + + + + +outputFileId +inputStartLine + + +12 + + +1 +3 +9 + + +3 +4 +19 + + +4 +8 +9 + + +8 +12 +8 + + +12 +17 +9 + + +18 +23 +9 + + +23 +28 +10 + + +28 +34 +8 + + +34 +47 +9 + + +47 +63 +9 + + +64 +79 +9 + + +82 +147 +8 + + + + + + +outputFileId +inputLineCount + + +12 + + +1 +2 +5 + + +2 +3 +9 + + +3 +4 +30 + + +4 +5 +14 + + +5 +6 +11 + + +6 +7 +13 + + +7 +8 +7 + + +8 +9 +7 + + +9 +10 +8 + + +10 +13 +9 + + +14 +19 +3 + + + + + + +outputFileId +outputStartLine + + +12 + + +1 +3 +9 + + +3 +4 +19 + + +4 +10 +9 + + +10 +16 +9 + + +16 +26 +9 + + +26 +30 +10 + + +30 +38 +9 + + +39 +53 +9 + + +53 +71 +9 + + +71 +97 +10 + + +107 +144 +9 + + +151 +229 +5 + + + + + + +outputFileId +outputLineIncrement + + +12 + + +1 +2 +11 + + +2 +3 +22 + + +3 +5 +9 + + +5 +6 +11 + + +6 +7 +10 + + +7 +8 +22 + + +8 +9 +10 + + +9 +10 +7 + + +10 +12 +10 + + +12 +15 +4 + + + + + + +stratum +outputFileId + + +12 + + +116 +117 +1 + + + + + + +stratum +inputFileNum + + +12 + + +1 +2 +1 + + + + + + +stratum +inputStartLine + + +12 + + +354 +355 +1 + + + + + + +stratum +inputLineCount + + +12 + + +57 +58 +1 + + + + + + +stratum +outputStartLine + + +12 + + +1873 +1874 +1 + + + + + + +stratum +outputLineIncrement + + +12 + + +19 +20 +1 + + + + + + +inputFileNum +outputFileId + + +12 + + +116 +117 +1 + + + + + + +inputFileNum +stratum + + +12 + + +1 +2 +1 + + + + + + +inputFileNum +inputStartLine + + +12 + + +354 +355 +1 + + + + + + +inputFileNum +inputLineCount + + +12 + + +57 +58 +1 + + + + + + +inputFileNum +outputStartLine + + +12 + + +1873 +1874 +1 + + + + + + +inputFileNum +outputLineIncrement + + +12 + + +19 +20 +1 + + + + + + +inputStartLine +outputFileId + + +12 + + +1 +2 +55 + + +2 +3 +50 + + +3 +4 +32 + + +4 +6 +30 + + +6 +8 +26 + + +8 +11 +22 + + +11 +14 +29 + + +14 +16 +29 + + +16 +19 +31 + + +19 +26 +28 + + +26 +46 +22 + + + + + + +inputStartLine +stratum + + +12 + + +1 +2 +354 + + + + + + +inputStartLine +inputFileNum + + +12 + + +1 +2 +354 + + + + + + +inputStartLine +inputLineCount + + +12 + + +1 +2 +53 + + +2 +3 +85 + + +3 +4 +46 + + +4 +5 +58 + + +5 +6 +59 + + +6 +7 +32 + + +7 +14 +21 + + + + + + +inputStartLine +outputStartLine + + +12 + + +1 +2 +39 + + +2 +3 +27 + + +3 +4 +41 + + +4 +5 +18 + + +5 +6 +21 + + +6 +9 +27 + + +9 +13 +29 + + +13 +18 +26 + + +18 +21 +26 + + +21 +24 +28 + + +24 +27 +29 + + +27 +36 +27 + + +36 +59 +16 + + + + + + +inputStartLine +outputLineIncrement + + +12 + + +1 +2 +45 + + +2 +3 +56 + + +3 +4 +53 + + +4 +5 +44 + + +5 +6 +52 + + +6 +7 +34 + + +7 +8 +26 + + +8 +9 +23 + + +9 +12 +21 + + + + + + +inputLineCount +outputFileId + + +12 + + +1 +2 +29 + + +2 +3 +7 + + +3 +4 +4 + + +4 +5 +2 + + +6 +8 +5 + + +12 +41 +5 + + +49 +112 +5 + + + + + + +inputLineCount +stratum + + +12 + + +1 +2 +57 + + + + + + +inputLineCount +inputFileNum + + +12 + + +1 +2 +57 + + + + + + +inputLineCount +inputStartLine + + +12 + + +1 +2 +29 + + +2 +3 +8 + + +3 +5 +5 + + +5 +7 +4 + + +8 +56 +5 + + +61 +236 +5 + + +336 +337 +1 + + + + + + +inputLineCount +outputStartLine + + +12 + + +1 +2 +29 + + +2 +3 +8 + + +3 +5 +5 + + +5 +8 +5 + + +10 +68 +5 + + +118 +1613 +5 + + + + + + +inputLineCount +outputLineIncrement + + +12 + + +1 +2 +53 + + +2 +20 +4 + + + + + + +outputStartLine +outputFileId + + +12 + + +1 +2 +848 + + +2 +3 +373 + + +3 +4 +214 + + +4 +5 +129 + + +5 +6 +108 + + +6 +9 +154 + + +9 +15 +47 + + + + + + +outputStartLine +stratum + + +12 + + +1 +2 +1873 + + + + + + +outputStartLine +inputFileNum + + +12 + + +1 +2 +1873 + + + + + + +outputStartLine +inputStartLine + + +12 + + +1 +2 +853 + + +2 +3 +379 + + +3 +4 +213 + + +4 +5 +137 + + +5 +6 +115 + + +6 +9 +148 + + +9 +14 +28 + + + + + + +outputStartLine +inputLineCount + + +12 + + +1 +2 +1225 + + +2 +3 +336 + + +3 +4 +169 + + +4 +7 +143 + + + + + + +outputStartLine +outputLineIncrement + + +12 + + +1 +2 +1013 + + +2 +3 +491 + + +3 +4 +247 + + +4 +9 +122 + + + + + + +outputLineIncrement +outputFileId + + +12 + + +2 +3 +2 + + +5 +6 +2 + + +6 +7 +1 + + +9 +10 +2 + + +15 +16 +1 + + +16 +17 +1 + + +19 +20 +1 + + +27 +28 +1 + + +29 +30 +1 + + +41 +42 +1 + + +52 +53 +2 + + +75 +76 +1 + + +79 +80 +1 + + +98 +99 +1 + + +112 +113 +1 + + + + + + +outputLineIncrement +stratum + + +12 + + +1 +2 +19 + + + + + + +outputLineIncrement +inputFileNum + + +12 + + +1 +2 +19 + + + + + + +outputLineIncrement +inputStartLine + + +12 + + +2 +3 +1 + + +4 +5 +1 + + +5 +6 +1 + + +6 +7 +1 + + +7 +8 +1 + + +13 +14 +1 + + +14 +15 +1 + + +24 +25 +1 + + +29 +30 +1 + + +32 +33 +1 + + +50 +51 +1 + + +56 +57 +1 + + +57 +58 +1 + + +121 +122 +1 + + +129 +130 +1 + + +180 +181 +1 + + +202 +203 +1 + + +266 +267 +1 + + +326 +327 +1 + + + + + + +outputLineIncrement +inputLineCount + + +12 + + +1 +2 +17 + + +4 +5 +1 + + +57 +58 +1 + + + + + + +outputLineIncrement +outputStartLine + + +12 + + +2 +3 +1 + + +4 +5 +1 + + +5 +6 +1 + + +6 +7 +1 + + +7 +8 +1 + + +13 +14 +1 + + +15 +16 +1 + + +24 +25 +1 + + +31 +32 +1 + + +36 +37 +1 + + +55 +56 +1 + + +64 +65 +1 + + +83 +84 +1 + + +155 +156 +1 + + +176 +177 +1 + + +405 +406 +1 + + +416 +417 +1 + + +798 +799 +1 + + +977 +978 +1 + + + + + + + + externalData 3485 From c077ca3fc9a0106230c6fc08a738b56d13d1fe89 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Thu, 19 Nov 2020 17:51:56 -0500 Subject: [PATCH 83/97] Java: add dbscheme upgrade script for SMAP relations --- .../old.dbscheme | 954 +++++++++++++++++ .../semmlecode.dbscheme | 982 ++++++++++++++++++ .../upgrade.properties | 2 + 3 files changed, 1938 insertions(+) create mode 100755 java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/old.dbscheme create mode 100755 java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/semmlecode.dbscheme create mode 100644 java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/upgrade.properties diff --git a/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/old.dbscheme b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/old.dbscheme new file mode 100755 index 00000000000..2a682863863 --- /dev/null +++ b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/old.dbscheme @@ -0,0 +1,954 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref // deprecated +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +classes( + unique int id: @class, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @class ref +); + +isRecord( + unique int id: @class ref +); + +interfaces( + unique int id: @interface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @interface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @interface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @class ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @typeorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @typeorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @class ref, + int parent: @classinstancexpr ref +); + +#keyset[classid] #keyset[parent] +isLocalClass( + int classid: @class ref, + int parent: @localclassdeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @interface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @typeorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localclassdeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +; + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@typeorpackage = @type | @package; + +@typeorcallable = @type | @callable; +@classorinterface = @interface | @class; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype; +@classorarray = @class | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; +@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field | + @annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl; + +@modifiable = @member_modifiable| @param | @localvar ; + +@member_modifiable = @class | @interface | @method | @constructor | @field ; + +@member = @method | @constructor | @field | @reftype ; + +@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception + | @boundedtype | @typebound | @array | @primitive + | @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText + | @xmllocatable; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; diff --git a/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/semmlecode.dbscheme b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/semmlecode.dbscheme new file mode 100755 index 00000000000..60a4ba1a475 --- /dev/null +++ b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/semmlecode.dbscheme @@ -0,0 +1,982 @@ +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * javac A.java B.java C.java + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + /** + * An invocation of the compiler. Note that more than one file may + * be compiled per invocation. For example, this command compiles + * three source files: + * + * javac A.java B.java C.java + */ + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | *path to extractor* + * 1 | `--javac-args` + * 2 | A.java + * 3 | B.java + * 4 | C.java + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * javac A.java B.java C.java + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | A.java + * 1 | B.java + * 2 | C.java + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * SMAP + */ + +smap_header( + int outputFileId: @file ref, + string outputFilename: string ref, + string defaultStratum: string ref +); + +smap_files( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + string inputFileName: string ref, + int inputFileId: @file ref +); + +smap_lines( + int outputFileId: @file ref, + string stratum: string ref, + int inputFileNum: int ref, + int inputStartLine: int ref, + int inputLineCount: int ref, + int outputStartLine: int ref, + int outputLineIncrement: int ref +); + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref // deprecated +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +classes( + unique int id: @class, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @class ref +); + +isRecord( + unique int id: @class ref +); + +interfaces( + unique int id: @interface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @interface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @interface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @class ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @typeorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @typeorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @class ref, + int parent: @classinstancexpr ref +); + +#keyset[classid] #keyset[parent] +isLocalClass( + int classid: @class ref, + int parent: @localclassdeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @interface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @typeorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt | @switchexpr; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localclassdeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +| 23 = @yieldstmt +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @exprparent ref, + int idx: int ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +isParenthesized( + unique int id: @expr ref, + int parentheses: int ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr // deprecated +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +| 73 = @switchexpr +; + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@typeorpackage = @type | @package; + +@typeorcallable = @type | @callable; +@classorinterface = @interface | @class; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype; +@classorarray = @class | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; +@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field | + @annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl; + +@modifiable = @member_modifiable| @param | @localvar ; + +@member_modifiable = @class | @interface | @method | @constructor | @field ; + +@member = @method | @constructor | @field | @reftype ; + +@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception + | @boundedtype | @typebound | @array | @primitive + | @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText + | @xmllocatable; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; diff --git a/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/upgrade.properties b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/upgrade.properties new file mode 100644 index 00000000000..976659f5e90 --- /dev/null +++ b/java/upgrades/2a682863863cf7641d54f762070a5e682847d1ca/upgrade.properties @@ -0,0 +1,2 @@ +description: Java: add SMAP relations +compatibility: backwards From 9bb949a8b13219e5bffd895ff843e0aac4d342a9 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Tue, 24 Nov 2020 21:35:30 -0500 Subject: [PATCH 84/97] Java: make some SMAP predicates `private` and add QLDoc --- java/ql/src/semmle/code/Location.qll | 4 ++-- java/ql/src/semmle/code/SMAP.qll | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/java/ql/src/semmle/code/Location.qll b/java/ql/src/semmle/code/Location.qll index ea78436d4f4..e9b808f6bba 100755 --- a/java/ql/src/semmle/code/Location.qll +++ b/java/ql/src/semmle/code/Location.qll @@ -6,7 +6,7 @@ import FileSystem import semmle.code.java.Element -import semmle.code.SMAP +private import semmle.code.SMAP /** Holds if element `e` has name `name`. */ predicate hasName(Element e, string name) { @@ -72,7 +72,7 @@ class Top extends @top { ) } - predicate hasLocationInfoAux( + private predicate hasLocationInfoAux( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { exists(File f, Location l | fixedHasLocation(this, l, f) | diff --git a/java/ql/src/semmle/code/SMAP.qll b/java/ql/src/semmle/code/SMAP.qll index c39f1a79797..d7ac1aa6fd0 100644 --- a/java/ql/src/semmle/code/SMAP.qll +++ b/java/ql/src/semmle/code/SMAP.qll @@ -1,6 +1,14 @@ +/** + * Provides classes and predicates for working with SMAP files (see JSR-045). + */ + import java -predicate smap(File inputFile, int inLine, File outputFile, int outLineStart, int outLineEnd) { +/** + * Holds if there exists a mapping between an SMAP input file and line + * and a corresponding SMAP output file and line range. + */ +private predicate smap(File inputFile, int inLine, File outputFile, int outLineStart, int outLineEnd) { exists( string defaultStratum, int inputFileNum, int inStart, int inCount, int outStart, int outIncr, int n @@ -15,13 +23,24 @@ predicate smap(File inputFile, int inLine, File outputFile, int outLineStart, in ) } -predicate smap(File inputFile, int inLine, File outputFile, int outLine) { +/** + * Holds if there exists a mapping between an SMAP input file and line + * and a corresponding SMAP output file and line. + */ +private predicate smap(File inputFile, int inLine, File outputFile, int outLine) { exists(int outLineStart, int outLineEnd | smap(inputFile, inLine, outputFile, outLineStart, outLineEnd) and outLine in [outLineStart .. outLineEnd] ) } +/** + * Holds if an SMAP input location (with path, line and column information) + * has a corresponding SMAP output location (with path and line information). + * + * For example, an SMAP input location may be a location within a JSP file, + * which may have a corresponding SMAP output location in generated Java code. + */ predicate hasSmapLocationInfo( string inputPath, int isl, int isc, int iel, int iec, string outputPath, int osl, int oel ) { From 2234d665ce109acc5f389ed8352d43625c61d6b5 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 26 Nov 2020 10:15:25 +0100 Subject: [PATCH 85/97] Add manual magic --- java/ql/src/semmle/code/SMAP.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/ql/src/semmle/code/SMAP.qll b/java/ql/src/semmle/code/SMAP.qll index d7ac1aa6fd0..006f5ad5e38 100644 --- a/java/ql/src/semmle/code/SMAP.qll +++ b/java/ql/src/semmle/code/SMAP.qll @@ -27,6 +27,7 @@ private predicate smap(File inputFile, int inLine, File outputFile, int outLineS * Holds if there exists a mapping between an SMAP input file and line * and a corresponding SMAP output file and line. */ +pragma[nomagic] private predicate smap(File inputFile, int inLine, File outputFile, int outLine) { exists(int outLineStart, int outLineEnd | smap(inputFile, inLine, outputFile, outLineStart, outLineEnd) and @@ -47,6 +48,7 @@ predicate hasSmapLocationInfo( exists(File inputFile, File outputFile | inputPath = inputFile.getAbsolutePath() and outputPath = outputFile.getAbsolutePath() and + locations_default(_, outputFile, osl, _, oel, _) and smap(inputFile, isl, outputFile, osl) and smap(inputFile, iel - 1, outputFile, oel) and isc = 1 and From 144e9e627110d114523be5c4eb1ca6984ade25e8 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 5 Nov 2020 12:18:14 +0100 Subject: [PATCH 86/97] C#: C#9 Add target typed conditional tests --- .../library-tests/csharp9/PrintAst.expected | 52 +++++++++++++++++++ .../test/library-tests/csharp9/TargetType.cs | 20 +++++++ .../library-tests/csharp9/TargetType.expected | 2 + .../test/library-tests/csharp9/TargetType.ql | 7 +++ 4 files changed, 81 insertions(+) create mode 100644 csharp/ql/test/library-tests/csharp9/TargetType.cs create mode 100644 csharp/ql/test/library-tests/csharp9/TargetType.expected create mode 100644 csharp/ql/test/library-tests/csharp9/TargetType.ql diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index cd5b3a656a7..0236321d43b 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -335,6 +335,58 @@ ParenthesizedPattern.cs: # 27| 0: [TypeAccessPatternExpr] access to type String # 27| 0: [TypeMention] string # 27| 2: [IntLiteral] 5 +TargetType.cs: +# 5| [Class] TargetType +# 7| 5: [Method] M2 +# 7| -1: [TypeMention] Void +# 8| 4: [BlockStmt] {...} +# 9| 0: [LocalVariableDeclStmt] ... ...; +# 9| 0: [LocalVariableDeclAndInitExpr] Random rand = ... +# 9| -1: [TypeMention] Random +# 9| 0: [LocalVariableAccess] access to local variable rand +# 9| 1: [ObjectCreation] object creation of type Random +# 9| 0: [TypeMention] Random +# 10| 1: [LocalVariableDeclStmt] ... ...; +# 10| 0: [LocalVariableDeclAndInitExpr] Boolean condition = ... +# 10| -1: [TypeMention] bool +# 10| 0: [LocalVariableAccess] access to local variable condition +# 10| 1: [GTExpr] ... > ... +# 10| 0: [MethodCall] call to method NextDouble +# 10| -1: [LocalVariableAccess] access to local variable rand +# 10| 1: [DoubleLiteral] 0.5 +# 12| 2: [LocalVariableDeclStmt] ... ...; +# 12| 0: [LocalVariableDeclAndInitExpr] Nullable x = ... +# 12| -1: [TypeMention] int? +# 12| 1: [TypeMention] int +# 12| 0: [LocalVariableAccess] access to local variable x +# 12| 1: [ConditionalExpr] ... ? ... : ... +# 12| 0: [LocalVariableAccess] access to local variable condition +# 13| 1: [CastExpr] (...) ... +# 13| 1: [IntLiteral] 12 +# 14| 2: [NullLiteral] null +# 16| 3: [LocalVariableDeclStmt] ... ...; +# 16| 0: [LocalVariableDeclAndInitExpr] IEnumerable xs = ... +# 16| -1: [TypeMention] IEnumerable +# 16| 1: [TypeMention] int +# 16| 0: [LocalVariableAccess] access to local variable xs +# 16| 1: [ConditionalExpr] ... ? ... : ... +# 16| 0: [IsExpr] ... is ... +# 16| 0: [LocalVariableAccess] access to local variable x +# 16| 1: [ConstantPatternExpr,NullLiteral] null +# 17| 1: [ObjectCreation] object creation of type List +# 17| -2: [TypeMention] List +# 17| 1: [TypeMention] int +# 17| -1: [CollectionInitializer] { ..., ... } +# 17| 0: [ElementInitializer] call to method Add +# 17| 0: [IntLiteral] 0 +# 17| 1: [ElementInitializer] call to method Add +# 17| 0: [IntLiteral] 1 +# 18| 2: [ArrayCreation] array creation of type Int32[] +# 18| -2: [TypeMention] Int32[] +# 18| 1: [TypeMention] int +# 18| -1: [ArrayInitializer] { ..., ... } +# 18| 0: [IntLiteral] 2 +# 18| 1: [IntLiteral] 3 TypeParameterNullability.cs: # 1| [Interface] I1 # 3| [Class] A2 diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.cs b/csharp/ql/test/library-tests/csharp9/TargetType.cs new file mode 100644 index 00000000000..758d915ec55 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/TargetType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public class TargetType +{ + public void M2() + { + var rand = new Random(); + var condition = rand.NextDouble() > 0.5; + + int? x = condition + ? 12 + : null; + + IEnumerable xs = x is null + ? new List() { 0, 1 } + : new int[] { 2, 3 }; + } +} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.expected b/csharp/ql/test/library-tests/csharp9/TargetType.expected new file mode 100644 index 00000000000..20b12551518 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/TargetType.expected @@ -0,0 +1,2 @@ +| TargetType.cs:12:18:14:18 | ... ? ... : ... | int? | int? | null | +| TargetType.cs:16:31:18:32 | ... ? ... : ... | IEnumerable | List | Int32[] | diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.ql b/csharp/ql/test/library-tests/csharp9/TargetType.ql new file mode 100644 index 00000000000..9d629a36a67 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/TargetType.ql @@ -0,0 +1,7 @@ +import csharp + +query predicate conditional(ConditionalExpr expr, string t, string t1, string t2) { + expr.getType().toStringWithTypes() = t and + expr.getThen().getType().toStringWithTypes() = t1 and + expr.getElse().getType().toStringWithTypes() = t2 +} From 548f276e1f4d568c545496adc6dacd4b4a4ee538 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Thu, 26 Nov 2020 12:15:21 +0100 Subject: [PATCH 87/97] Add more tests --- .../library-tests/csharp9/PrintAst.expected | 143 ++++++++++++++---- .../test/library-tests/csharp9/TargetType.cs | 25 ++- .../library-tests/csharp9/TargetType.expected | 16 +- .../test/library-tests/csharp9/TargetType.ql | 16 ++ 4 files changed, 168 insertions(+), 32 deletions(-) diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 0236321d43b..42a0fad8d14 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -355,38 +355,125 @@ TargetType.cs: # 10| -1: [LocalVariableAccess] access to local variable rand # 10| 1: [DoubleLiteral] 0.5 # 12| 2: [LocalVariableDeclStmt] ... ...; -# 12| 0: [LocalVariableDeclAndInitExpr] Nullable x = ... +# 12| 0: [LocalVariableDeclAndInitExpr] Nullable x0 = ... # 12| -1: [TypeMention] int? # 12| 1: [TypeMention] int -# 12| 0: [LocalVariableAccess] access to local variable x -# 12| 1: [ConditionalExpr] ... ? ... : ... -# 12| 0: [LocalVariableAccess] access to local variable condition -# 13| 1: [CastExpr] (...) ... -# 13| 1: [IntLiteral] 12 -# 14| 2: [NullLiteral] null -# 16| 3: [LocalVariableDeclStmt] ... ...; -# 16| 0: [LocalVariableDeclAndInitExpr] IEnumerable xs = ... -# 16| -1: [TypeMention] IEnumerable +# 12| 0: [LocalVariableAccess] access to local variable x0 +# 12| 1: [CastExpr] (...) ... +# 12| 1: [IntLiteral] 12 +# 13| 3: [ExprStmt] ...; +# 13| 0: [AssignExpr] ... = ... +# 13| 0: [LocalVariableAccess] access to local variable x0 +# 13| 1: [CastExpr] (...) ... +# 13| 1: [IntLiteral] 13 +# 14| 4: [LocalVariableDeclStmt] ... ...; +# 14| 0: [LocalVariableDeclAndInitExpr] Nullable x1 = ... +# 14| -1: [TypeMention] int? +# 14| 1: [TypeMention] int +# 14| 0: [LocalVariableAccess] access to local variable x1 +# 14| 1: [NullLiteral] null +# 16| 5: [LocalVariableDeclStmt] ... ...; +# 16| 0: [LocalVariableDeclAndInitExpr] Nullable x2 = ... +# 16| -1: [TypeMention] int? # 16| 1: [TypeMention] int -# 16| 0: [LocalVariableAccess] access to local variable xs +# 16| 0: [LocalVariableAccess] access to local variable x2 # 16| 1: [ConditionalExpr] ... ? ... : ... -# 16| 0: [IsExpr] ... is ... -# 16| 0: [LocalVariableAccess] access to local variable x -# 16| 1: [ConstantPatternExpr,NullLiteral] null -# 17| 1: [ObjectCreation] object creation of type List -# 17| -2: [TypeMention] List -# 17| 1: [TypeMention] int -# 17| -1: [CollectionInitializer] { ..., ... } -# 17| 0: [ElementInitializer] call to method Add -# 17| 0: [IntLiteral] 0 -# 17| 1: [ElementInitializer] call to method Add -# 17| 0: [IntLiteral] 1 -# 18| 2: [ArrayCreation] array creation of type Int32[] -# 18| -2: [TypeMention] Int32[] -# 18| 1: [TypeMention] int -# 18| -1: [ArrayInitializer] { ..., ... } -# 18| 0: [IntLiteral] 2 -# 18| 1: [IntLiteral] 3 +# 16| 0: [LocalVariableAccess] access to local variable condition +# 17| 1: [CastExpr] (...) ... +# 17| 1: [IntLiteral] 12 +# 18| 2: [NullLiteral] null +# 20| 6: [LocalVariableDeclStmt] ... ...; +# 20| 0: [LocalVariableDeclAndInitExpr] Nullable x3 = ... +# 20| -1: [TypeMention] int? +# 20| 1: [TypeMention] int +# 20| 0: [LocalVariableAccess] access to local variable x3 +# 20| 1: [ConditionalExpr] ... ? ... : ... +# 20| 0: [LocalVariableAccess] access to local variable condition +# 21| 1: [CastExpr] (...) ... +# 21| 0: [TypeAccess] access to type Nullable +# 21| 0: [TypeMention] int? +# 21| 1: [TypeMention] int +# 21| 1: [IntLiteral] 12 +# 22| 2: [NullLiteral] null +# 24| 7: [LocalVariableDeclStmt] ... ...; +# 24| 0: [LocalVariableDeclAndInitExpr] Nullable x4 = ... +# 24| -1: [TypeMention] int? +# 24| 1: [TypeMention] int +# 24| 0: [LocalVariableAccess] access to local variable x4 +# 24| 1: [ConditionalExpr] ... ? ... : ... +# 24| 0: [LocalVariableAccess] access to local variable condition +# 25| 1: [CastExpr] (...) ... +# 25| 1: [IntLiteral] 12 +# 26| 2: [CastExpr] (...) ... +# 26| 0: [TypeAccess] access to type Nullable +# 26| 0: [TypeMention] int? +# 26| 1: [TypeMention] int +# 26| 1: [NullLiteral] null +# 28| 8: [LocalVariableDeclStmt] ... ...; +# 28| 0: [LocalVariableDeclAndInitExpr] IEnumerable xs0 = ... +# 28| -1: [TypeMention] IEnumerable +# 28| 1: [TypeMention] int +# 28| 0: [LocalVariableAccess] access to local variable xs0 +# 28| 1: [ObjectCreation] object creation of type List +# 28| -2: [TypeMention] List +# 28| 1: [TypeMention] int +# 28| -1: [CollectionInitializer] { ..., ... } +# 28| 0: [ElementInitializer] call to method Add +# 28| 0: [IntLiteral] 0 +# 28| 1: [ElementInitializer] call to method Add +# 28| 0: [IntLiteral] 1 +# 29| 9: [LocalVariableDeclStmt] ... ...; +# 29| 0: [LocalVariableDeclAndInitExpr] IEnumerable xs1 = ... +# 29| -1: [TypeMention] IEnumerable +# 29| 1: [TypeMention] int +# 29| 0: [LocalVariableAccess] access to local variable xs1 +# 29| 1: [ArrayCreation] array creation of type Int32[] +# 29| -2: [TypeMention] Int32[] +# 29| 1: [TypeMention] int +# 29| -1: [ArrayInitializer] { ..., ... } +# 29| 0: [IntLiteral] 2 +# 29| 1: [IntLiteral] 3 +# 31| 10: [LocalVariableDeclStmt] ... ...; +# 31| 0: [LocalVariableDeclAndInitExpr] IEnumerable xs2 = ... +# 31| -1: [TypeMention] IEnumerable +# 31| 1: [TypeMention] int +# 31| 0: [LocalVariableAccess] access to local variable xs2 +# 31| 1: [ConditionalExpr] ... ? ... : ... +# 31| 0: [IsExpr] ... is ... +# 31| 0: [LocalVariableAccess] access to local variable x2 +# 31| 1: [ConstantPatternExpr,NullLiteral] null +# 32| 1: [ObjectCreation] object creation of type List +# 32| -2: [TypeMention] List +# 32| 1: [TypeMention] int +# 32| -1: [CollectionInitializer] { ..., ... } +# 32| 0: [ElementInitializer] call to method Add +# 32| 0: [IntLiteral] 0 +# 32| 1: [ElementInitializer] call to method Add +# 32| 0: [IntLiteral] 1 +# 33| 2: [ArrayCreation] array creation of type Int32[] +# 33| -2: [TypeMention] Int32[] +# 33| 1: [TypeMention] int +# 33| -1: [ArrayInitializer] { ..., ... } +# 33| 0: [IntLiteral] 2 +# 33| 1: [IntLiteral] 3 +# 35| 11: [LocalVariableDeclStmt] ... ...; +# 35| 0: [LocalVariableDeclAndInitExpr] Nullable c = ... +# 35| -1: [TypeMention] int? +# 35| 1: [TypeMention] int +# 35| 0: [LocalVariableAccess] access to local variable c +# 35| 1: [CastExpr] (...) ... +# 35| 1: [ConditionalExpr] ... ? ... : ... +# 35| 0: [LocalVariableAccess] access to local variable condition +# 36| 1: [OperatorCall] call to operator implicit conversion +# 36| 0: [ObjectCreation] object creation of type TargetType +# 36| 0: [TypeMention] TargetType +# 37| 2: [IntLiteral] 12 +# 40| 6: [ImplicitConversionOperator] implicit conversion +# 40| -1: [TypeMention] int +#-----| 2: (Parameters) +# 40| 0: [Parameter] d +# 40| -1: [TypeMention] TargetType +# 40| 4: [IntLiteral] 0 TypeParameterNullability.cs: # 1| [Interface] I1 # 3| [Class] A2 diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.cs b/csharp/ql/test/library-tests/csharp9/TargetType.cs index 758d915ec55..96a236ac3a8 100644 --- a/csharp/ql/test/library-tests/csharp9/TargetType.cs +++ b/csharp/ql/test/library-tests/csharp9/TargetType.cs @@ -9,12 +9,33 @@ public class TargetType var rand = new Random(); var condition = rand.NextDouble() > 0.5; - int? x = condition + int? x0 = 12; + x0 = 13; + int? x1 = null; + + int? x2 = condition ? 12 : null; - IEnumerable xs = x is null + int? x3 = condition + ? (int?)12 + : null; + + int? x4 = condition + ? 12 + : (int?)null; + + IEnumerable xs0 = new List() { 0, 1 }; + IEnumerable xs1 = new int[] { 2, 3 }; + + IEnumerable xs2 = x2 is null ? new List() { 0, 1 } : new int[] { 2, 3 }; + + int? c = condition + ? new TargetType() + : 12; } + + public static implicit operator int(TargetType d) => 0; } \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.expected b/csharp/ql/test/library-tests/csharp9/TargetType.expected index 20b12551518..52bb8406125 100644 --- a/csharp/ql/test/library-tests/csharp9/TargetType.expected +++ b/csharp/ql/test/library-tests/csharp9/TargetType.expected @@ -1,2 +1,14 @@ -| TargetType.cs:12:18:14:18 | ... ? ... : ... | int? | int? | null | -| TargetType.cs:16:31:18:32 | ... ? ... : ... | IEnumerable | List | Int32[] | +conditional +| TargetType.cs:16:19:18:18 | ... ? ... : ... | int? | int? | null | +| TargetType.cs:20:19:22:18 | ... ? ... : ... | int? | int? | null | +| TargetType.cs:24:19:26:24 | ... ? ... : ... | int? | int? | int? | +| TargetType.cs:31:32:33:32 | ... ? ... : ... | IEnumerable | List | Int32[] | +| TargetType.cs:35:18:37:16 | ... ? ... : ... | int | int | int | +implicitCasts +| TargetType.cs:12:19:12:20 | (...) ... | int? | int | +| TargetType.cs:13:14:13:15 | (...) ... | int? | int | +| TargetType.cs:17:15:17:16 | (...) ... | int? | int | +| TargetType.cs:25:15:25:16 | (...) ... | int? | int | +| TargetType.cs:35:18:37:16 | (...) ... | int? | int | +implicitConversions +| TargetType.cs:36:15:36:30 | call to operator implicit conversion | int | TargetType | diff --git a/csharp/ql/test/library-tests/csharp9/TargetType.ql b/csharp/ql/test/library-tests/csharp9/TargetType.ql index 9d629a36a67..95027ece89f 100644 --- a/csharp/ql/test/library-tests/csharp9/TargetType.ql +++ b/csharp/ql/test/library-tests/csharp9/TargetType.ql @@ -5,3 +5,19 @@ query predicate conditional(ConditionalExpr expr, string t, string t1, string t2 expr.getThen().getType().toStringWithTypes() = t1 and expr.getElse().getType().toStringWithTypes() = t2 } + +query predicate implicitCasts(CastExpr expr, string type, string childType) { + expr.getFile().getStem() = "TargetType" and + expr.fromSource() and + not exists(expr.getTypeAccess()) and + type = expr.getType().toStringWithTypes() and + childType = expr.getExpr().getType().toStringWithTypes() +} + +query predicate implicitConversions(OperatorCall opCall, string type, string childType) { + opCall.getFile().getStem() = "TargetType" and + opCall.fromSource() and + opCall.getTarget().getFunctionName() = "op_Implicit" and + opCall.getTarget().getReturnType().toStringWithTypes() = type and + opCall.getAnArgument().getType().toStringWithTypes() = childType +} From b11fc2f9577755284afab28f02bbbfbbc4d2cc05 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Wed, 11 Nov 2020 13:44:37 +0100 Subject: [PATCH 88/97] C#: Extract relational patterns --- .../2020-11-19-Relational-pattern.md | 3 + .../Entities/Expressions/Patterns/Pattern.cs | 3 + .../Expressions/Patterns/RecursivePattern.cs | 2 +- .../Expressions/Patterns/RelationalPattern.cs | 29 ++++++++++ .../Kinds/ExprKind.cs | 6 +- .../ql/src/semmle/code/csharp/exprs/Expr.qll | 39 +++++++++++++ csharp/ql/src/semmlecode.csharp.dbscheme | 6 ++ .../library-tests/csharp9/PrintAst.expected | 57 +++++++++++++++++++ .../csharp9/RelationalPattern.cs | 23 ++++++++ .../csharp9/relationalPattern.expected | 5 ++ .../csharp9/relationalPattern.ql | 4 ++ csharp/upgrades/TO_CHANGE/upgrade.properties | 2 + 12 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 csharp/change-notes/2020-11-19-Relational-pattern.md create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs create mode 100644 csharp/ql/test/library-tests/csharp9/RelationalPattern.cs create mode 100644 csharp/ql/test/library-tests/csharp9/relationalPattern.expected create mode 100644 csharp/ql/test/library-tests/csharp9/relationalPattern.ql create mode 100644 csharp/upgrades/TO_CHANGE/upgrade.properties diff --git a/csharp/change-notes/2020-11-19-Relational-pattern.md b/csharp/change-notes/2020-11-19-Relational-pattern.md new file mode 100644 index 00000000000..7839c2f3d8b --- /dev/null +++ b/csharp/change-notes/2020-11-19-Relational-pattern.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* The `RelationalPatternExpr` and its 4 subclass have been added to support C# 9 +relational `<`, `>`, `<=`, and `>=` patterns. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs index 7fe552ec8e9..3888ac00112 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs @@ -42,6 +42,9 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions case RecursivePatternSyntax recPattern: return new RecursivePattern(cx, recPattern, parent, child); + case RelationalPatternSyntax relPattern: + return new RelationalPattern(cx, relPattern, parent, child); + case VarPatternSyntax varPattern: switch (varPattern.Designation) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs index 997c622c818..198669547f1 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs @@ -6,7 +6,7 @@ using Semmle.Extraction.Entities; namespace Semmle.Extraction.CSharp.Entities.Expressions { - internal class RecursivePattern : Expression + internal partial class RecursivePattern : Expression { /// /// Creates and populates a recursive pattern. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs new file mode 100644 index 00000000000..9dce7bf7c52 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs @@ -0,0 +1,29 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Semmle.Extraction.Kinds; +using Semmle.Extraction.Entities; + +namespace Semmle.Extraction.CSharp.Entities.Expressions +{ + internal class RelationalPattern : Expression + { + public RelationalPattern(Context cx, RelationalPatternSyntax syntax, IExpressionParentEntity parent, int child) : + base(new ExpressionInfo(cx, NullType.Create(cx), cx.Create(syntax.GetLocation()), GetKind(syntax.OperatorToken), parent, child, false, null)) + { + Expression.Create(cx, syntax.Expression, this, 0); + } + + private static ExprKind GetKind(SyntaxToken operatorToken) + { + return operatorToken.Kind() switch + { + SyntaxKind.LessThanEqualsToken => ExprKind.LE_PATTERN, + SyntaxKind.GreaterThanEqualsToken => ExprKind.GE_PATTERN, + SyntaxKind.LessThanToken => ExprKind.LT_PATTERN, + SyntaxKind.GreaterThanToken => ExprKind.GT_PATTERN, + _ => throw new InternalError(operatorToken.Parent, $"Relation pattern with operator token '{operatorToken.Kind()}' is not supported."), + }; + } + } +} diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs index f46ed47ff5f..c7c22b6789d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs @@ -115,6 +115,10 @@ namespace Semmle.Extraction.Kinds SWITCH_CASE = 118, ASSIGN_COALESCE = 119, SUPPRESS_NULLABLE_WARNING = 120, - NAMESPACE_ACCESS = 121 + NAMESPACE_ACCESS = 121, + LT_PATTERN = 122, + GT_PATTERN = 123, + LE_PATTERN = 124, + GE_PATTERN = 125, } } diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll index b133b628732..0607b08830c 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll @@ -342,6 +342,45 @@ class ConstantPatternExpr extends PatternExpr { override string getAPrimaryQlClass() { result = "ConstantPatternExpr" } } +/** A relational pattern, for example `>1` in `x is >1`. */ +abstract class RelationalPatternExpr extends PatternExpr, @relational_pattern_expr { + /** Get the operator of this relational pattern. */ + string getOperator() { none() } + + /** Gets the expression of this relational pattern. */ + Expr getExpr() { result = this.getChild(0) } + + override string toString() { result = getOperator() + " ..." } +} + +/** A 'less than' pattern */ +class LTPattern extends RelationalPatternExpr, @lt_pattern_expr { + override string getOperator() { result = "<" } + + override string getAPrimaryQlClass() { result = "LTPattern" } +} + +/** A 'greater than' pattern */ +class GTPattern extends RelationalPatternExpr, @gt_pattern_expr { + override string getOperator() { result = ">" } + + override string getAPrimaryQlClass() { result = "GTPattern" } +} + +/** A 'less than equal' pattern */ +class LEPattern extends RelationalPatternExpr, @le_pattern_expr { + override string getOperator() { result = "<=" } + + override string getAPrimaryQlClass() { result = "LEPattern" } +} + +/** A 'greater than equal' pattern */ +class GEPattern extends RelationalPatternExpr, @ge_pattern_expr { + override string getOperator() { result = ">=" } + + override string getAPrimaryQlClass() { result = "GEPattern" } +} + /** * A type pattern, for example `string` in `x is string`, `string s` in * `x is string s`, or `string _` in `x is string _`. diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme index eedef9359e1..173895c0c82 100644 --- a/csharp/ql/src/semmlecode.csharp.dbscheme +++ b/csharp/ql/src/semmlecode.csharp.dbscheme @@ -998,11 +998,17 @@ case @expr.kind of | 119 = @assign_coalesce_expr | 120 = @suppress_nullable_warning_expr | 121 = @namespace_access_expr +/* C# 9.0 */ +| 122 = @lt_pattern_expr +| 123 = @gt_pattern_expr +| 124 = @le_pattern_expr +| 125 = @ge_pattern_expr ; @switch = @switch_stmt | @switch_expr; @case = @case_stmt | @switch_case_expr; @pattern_match = @case | @is_expr; +@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; @integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; @real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 42a0fad8d14..4eecbab929c 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -335,6 +335,63 @@ ParenthesizedPattern.cs: # 27| 0: [TypeAccessPatternExpr] access to type String # 27| 0: [TypeMention] string # 27| 2: [IntLiteral] 5 +RelationalPattern.cs: +# 3| [Class] RelationalPattern +# 5| 5: [Method] M1 +# 5| -1: [TypeMention] bool +#-----| 2: (Parameters) +# 5| 0: [Parameter] c +# 5| -1: [TypeMention] char +# 6| 4: [IsExpr] ... is ... +# 6| 0: [ParameterAccess] access to parameter c +# 6| 1: [GEPattern] >= ... +# 6| 0: [CharLiteral] a +# 7| 6: [Method] M2 +# 7| -1: [TypeMention] bool +#-----| 2: (Parameters) +# 7| 0: [Parameter] c +# 7| -1: [TypeMention] char +# 8| 4: [IsExpr] ... is ... +# 8| 0: [ParameterAccess] access to parameter c +# 8| 1: [GTPattern] > ... +# 8| 0: [CharLiteral] a +# 9| 7: [Method] M3 +# 9| -1: [TypeMention] bool +#-----| 2: (Parameters) +# 9| 0: [Parameter] c +# 9| -1: [TypeMention] char +# 10| 4: [IsExpr] ... is ... +# 10| 0: [ParameterAccess] access to parameter c +# 10| 1: [LEPattern] <= ... +# 10| 0: [CharLiteral] a +# 11| 8: [Method] M4 +# 11| -1: [TypeMention] bool +#-----| 2: (Parameters) +# 11| 0: [Parameter] c +# 11| -1: [TypeMention] char +# 12| 4: [IsExpr] ... is ... +# 12| 0: [ParameterAccess] access to parameter c +# 12| 1: [LTPattern] < ... +# 12| 0: [CharLiteral] a +# 14| 9: [Method] M5 +# 14| -1: [TypeMention] string +#-----| 2: (Parameters) +# 14| 0: [Parameter] i +# 14| -1: [TypeMention] int +# 15| 4: [BlockStmt] {...} +# 16| 0: [ReturnStmt] return ...; +# 16| 0: [SwitchExpr] ... switch { ... } +# 16| -1: [ParameterAccess] access to parameter i +# 18| 0: [SwitchCaseExpr] ... => ... +# 18| 0: [ConstantPatternExpr,IntLiteral] 1 +# 18| 2: [StringLiteral] "1" +# 19| 1: [SwitchCaseExpr] ... => ... +# 19| 0: [GTPattern] > ... +# 19| 0: [IntLiteral] 1 +# 19| 2: [StringLiteral] ">1" +# 20| 2: [SwitchCaseExpr] ... => ... +# 20| 0: [DiscardPatternExpr] _ +# 20| 2: [StringLiteral] "other" TargetType.cs: # 5| [Class] TargetType # 7| 5: [Method] M2 diff --git a/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs b/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs new file mode 100644 index 00000000000..d21f5044507 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs @@ -0,0 +1,23 @@ +using System; + +public class RelationalPattern +{ + public static bool M1(char c) => + c is >= 'a'; + public static bool M2(char c) => + c is > 'a'; + public static bool M3(char c) => + c is <= 'a'; + public static bool M4(char c) => + c is < 'a'; + + public static string M5(int i) + { + return i switch + { + 1 => "1", + >1 => ">1", + _ => "other" + }; + } +} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/relationalPattern.expected b/csharp/ql/test/library-tests/csharp9/relationalPattern.expected new file mode 100644 index 00000000000..43dca825928 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/relationalPattern.expected @@ -0,0 +1,5 @@ +| RelationalPattern.cs:6:14:6:19 | >= ... | >= | +| RelationalPattern.cs:8:14:8:18 | > ... | > | +| RelationalPattern.cs:10:14:10:19 | <= ... | <= | +| RelationalPattern.cs:12:14:12:18 | < ... | < | +| RelationalPattern.cs:19:13:19:14 | > ... | > | diff --git a/csharp/ql/test/library-tests/csharp9/relationalPattern.ql b/csharp/ql/test/library-tests/csharp9/relationalPattern.ql new file mode 100644 index 00000000000..50618e5bb7d --- /dev/null +++ b/csharp/ql/test/library-tests/csharp9/relationalPattern.ql @@ -0,0 +1,4 @@ +import csharp + +from RelationalPatternExpr p +select p, p.getOperator() diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/TO_CHANGE/upgrade.properties new file mode 100644 index 00000000000..ff3b5413d2c --- /dev/null +++ b/csharp/upgrades/TO_CHANGE/upgrade.properties @@ -0,0 +1,2 @@ +description: Add 'relational_pattern_expr' and its four subtypes ('<', '>', '<=', '>=') +compatibility: backwards From 5a808190d428cc943987c10ad8db92bf811d60c8 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Wed, 25 Nov 2020 21:40:41 +0100 Subject: [PATCH 89/97] Address review comments --- csharp/change-notes/2020-11-19-Relational-pattern.md | 2 +- .../Expressions/Patterns/RecursivePattern.cs | 3 +-- csharp/ql/src/semmle/code/csharp/exprs/Expr.qll | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/csharp/change-notes/2020-11-19-Relational-pattern.md b/csharp/change-notes/2020-11-19-Relational-pattern.md index 7839c2f3d8b..77179884d12 100644 --- a/csharp/change-notes/2020-11-19-Relational-pattern.md +++ b/csharp/change-notes/2020-11-19-Relational-pattern.md @@ -1,3 +1,3 @@ lgtm,codescanning -* The `RelationalPatternExpr` and its 4 subclass have been added to support C# 9 +* The `RelationalPatternExpr` and its 4 sub class have been added to support C# 9 relational `<`, `>`, `<=`, and `>=` patterns. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs index 198669547f1..c6045f8cb14 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs @@ -6,7 +6,7 @@ using Semmle.Extraction.Entities; namespace Semmle.Extraction.CSharp.Entities.Expressions { - internal partial class RecursivePattern : Expression + internal class RecursivePattern : Expression { /// /// Creates and populates a recursive pattern. @@ -15,7 +15,6 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions /// The syntax node of the recursive pattern. /// The parent pattern/expression. /// The child index of this pattern. - /// If this pattern is in the top level of a case/is. In that case, the variable and type access are populated elsewhere. public RecursivePattern(Context cx, RecursivePatternSyntax syntax, IExpressionParentEntity parent, int child) : base(new ExpressionInfo(cx, Entities.NullType.Create(cx), cx.Create(syntax.GetLocation()), ExprKind.RECURSIVE_PATTERN, parent, child, false, null)) { diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll index 0607b08830c..0f7dcb64a40 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll @@ -343,8 +343,8 @@ class ConstantPatternExpr extends PatternExpr { } /** A relational pattern, for example `>1` in `x is >1`. */ -abstract class RelationalPatternExpr extends PatternExpr, @relational_pattern_expr { - /** Get the operator of this relational pattern. */ +class RelationalPatternExpr extends PatternExpr, @relational_pattern_expr { + /** Gets the name of the operator in this pattern. */ string getOperator() { none() } /** Gets the expression of this relational pattern. */ @@ -353,28 +353,28 @@ abstract class RelationalPatternExpr extends PatternExpr, @relational_pattern_ex override string toString() { result = getOperator() + " ..." } } -/** A 'less than' pattern */ +/** A less-than pattern, for example `< 10` in `x is < 10`. */ class LTPattern extends RelationalPatternExpr, @lt_pattern_expr { override string getOperator() { result = "<" } override string getAPrimaryQlClass() { result = "LTPattern" } } -/** A 'greater than' pattern */ +/** A greater-than pattern, for example `> 10` in `x is > 10`. */ class GTPattern extends RelationalPatternExpr, @gt_pattern_expr { override string getOperator() { result = ">" } override string getAPrimaryQlClass() { result = "GTPattern" } } -/** A 'less than equal' pattern */ +/** A less-than or equals pattern, for example `<= 10` in `x is <= 10`. */ class LEPattern extends RelationalPatternExpr, @le_pattern_expr { override string getOperator() { result = "<=" } override string getAPrimaryQlClass() { result = "LEPattern" } } -/** A 'greater than equal' pattern */ +/** A greater-than or equals pattern, for example `>= 10` in `x is >= 10` */ class GEPattern extends RelationalPatternExpr, @ge_pattern_expr { override string getOperator() { result = ">=" } From 07c989deb17267e00c456c1b95d44bbc637dcdfc Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Fri, 27 Nov 2020 10:21:17 +0100 Subject: [PATCH 90/97] C#: Add upgrade folder --- .../old.dbscheme | 1890 ++++++++++++++++ .../semmlecode.csharp.dbscheme | 1896 +++++++++++++++++ .../upgrade.properties | 0 3 files changed, 3786 insertions(+) create mode 100644 csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme create mode 100644 csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme rename csharp/upgrades/{TO_CHANGE => eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160}/upgrade.properties (100%) diff --git a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme new file mode 100644 index 00000000000..eedef9359e1 --- /dev/null +++ b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme @@ -0,0 +1,1890 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme new file mode 100644 index 00000000000..173895c0c82 --- /dev/null +++ b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme @@ -0,0 +1,1896 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +/* C# 9.0 */ +| 122 = @lt_pattern_expr +| 123 = @gt_pattern_expr +| 124 = @le_pattern_expr +| 125 = @ge_pattern_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; +@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; + +#keyset[method, index] +cil_parameter( + unique int id: @cil_parameter, + int method: @cil_method ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/TO_CHANGE/upgrade.properties b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties similarity index 100% rename from csharp/upgrades/TO_CHANGE/upgrade.properties rename to csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties From 8069e7b0315d35fef496790d9542a0138d90411e Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 27 Nov 2020 10:40:09 +0100 Subject: [PATCH 91/97] C++: Downgrade two queries to recommendation The `cpp/local-variable-hides-global-variable` doesn't seem right as a warning without some additional context. For example, is the local variable and the global variable used in the same function body, and do they have similar enough types that it would be possible to confuse them. The `cpp/missing-header-guard` query enforces good style and helps with compilation speed, but AFAIK it has never flagged a correctness issue. Therefore I think it should be a recommendation. --- cpp/change-notes/2020-11-27-downgrade-to-recommendation.md | 2 ++ .../Best Practices/Hiding/LocalVariableHidesGlobalVariable.ql | 2 +- cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 cpp/change-notes/2020-11-27-downgrade-to-recommendation.md diff --git a/cpp/change-notes/2020-11-27-downgrade-to-recommendation.md b/cpp/change-notes/2020-11-27-downgrade-to-recommendation.md new file mode 100644 index 00000000000..519aba707ad --- /dev/null +++ b/cpp/change-notes/2020-11-27-downgrade-to-recommendation.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The queries `cpp/local-variable-hides-global-variable` and `cpp/missing-header-guard` now have severity `recommendation` instead of `warning`. diff --git a/cpp/ql/src/Best Practices/Hiding/LocalVariableHidesGlobalVariable.ql b/cpp/ql/src/Best Practices/Hiding/LocalVariableHidesGlobalVariable.ql index 3e688a744b1..53c96c4beb7 100644 --- a/cpp/ql/src/Best Practices/Hiding/LocalVariableHidesGlobalVariable.ql +++ b/cpp/ql/src/Best Practices/Hiding/LocalVariableHidesGlobalVariable.ql @@ -2,7 +2,7 @@ * @name Local variable hides global variable * @description A local variable or parameter that hides a global variable of the same name. This may be confusing. Consider renaming one of the variables. * @kind problem - * @problem.severity warning + * @problem.severity recommendation * @precision very-high * @id cpp/local-variable-hides-global-variable * @tags maintainability diff --git a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql index dbf896e187d..704c5baa067 100644 --- a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql +++ b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql @@ -4,7 +4,7 @@ * the file from being included twice). This prevents errors and * inefficiencies caused by repeated inclusion. * @kind problem - * @problem.severity warning + * @problem.severity recommendation * @precision high * @id cpp/missing-header-guard * @tags efficiency From 7b4e890e7b7ce24467249323f0d251ca1bb708c1 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 27 Nov 2020 11:00:30 +0100 Subject: [PATCH 92/97] Python: Fix grammar Co-authored-by: Taus --- .../wrong/module-imports/conflict-stdlib/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md index 6c80c3976dc..666e3c4bd2a 100644 --- a/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md +++ b/python/ql/test/library-tests/PointsTo/regressions/wrong/module-imports/conflict-stdlib/README.md @@ -1,6 +1,6 @@ -This test shows how we handle modules the shadow a module in the standard library. +This test shows how we handle modules that shadow a module in the standard library. -We manually replicate the behavior of `codeql database create --source-root `, which will use `-R `. By default, the way qltest invokes the extractor will cause different behavior. Therefore, we also need to move our code outside of the top-level folder, and it lives in `code-invalid-package-name/` -- notice that if we use `code` as the folder name, the extractor will treat it as if there is a package called `code` (note, `codeql database create` would not the folder `code` as a package when `code` is used as the `--source-root`). +We manually replicate the behavior of `codeql database create --source-root `, which will use `-R `. By default, the way qltest invokes the extractor will cause different behavior. Therefore, we also need to move our code outside of the top-level folder, and it lives in `code-invalid-package-name/` -- notice that if we use `code` as the folder name, the extractor will treat it as if there is a package called `code` (note, `codeql database create` would not treat the folder `code` as a package when `code` is used as the `--source-root`). The results from `LocalModules.ql`, where everything is a script, matches with the extractor :+1: From 8f4fce185b2e50621102e459be73eac2a4991175 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 27 Nov 2020 12:16:28 +0100 Subject: [PATCH 93/97] Dataflow: Review fixes. --- .../java/dataflow/internal/DataFlowImpl.qll | 86 +++++++++++-------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** From fec9758252d7e6d056fc81097180751eaba44c26 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 27 Nov 2020 12:16:43 +0100 Subject: [PATCH 94/97] Dataflow: Sync. --- .../cpp/dataflow/internal/DataFlowImpl.qll | 86 +++++++++++-------- .../cpp/dataflow/internal/DataFlowImpl2.qll | 86 +++++++++++-------- .../cpp/dataflow/internal/DataFlowImpl3.qll | 86 +++++++++++-------- .../cpp/dataflow/internal/DataFlowImpl4.qll | 86 +++++++++++-------- .../dataflow/internal/DataFlowImplLocal.qll | 86 +++++++++++-------- .../cpp/ir/dataflow/internal/DataFlowImpl.qll | 86 +++++++++++-------- .../ir/dataflow/internal/DataFlowImpl2.qll | 86 +++++++++++-------- .../ir/dataflow/internal/DataFlowImpl3.qll | 86 +++++++++++-------- .../ir/dataflow/internal/DataFlowImpl4.qll | 86 +++++++++++-------- .../csharp/dataflow/internal/DataFlowImpl.qll | 86 +++++++++++-------- .../dataflow/internal/DataFlowImpl2.qll | 86 +++++++++++-------- .../dataflow/internal/DataFlowImpl3.qll | 86 +++++++++++-------- .../dataflow/internal/DataFlowImpl4.qll | 86 +++++++++++-------- .../dataflow/internal/DataFlowImpl5.qll | 86 +++++++++++-------- .../java/dataflow/internal/DataFlowImpl2.qll | 86 +++++++++++-------- .../java/dataflow/internal/DataFlowImpl3.qll | 86 +++++++++++-------- .../java/dataflow/internal/DataFlowImpl4.qll | 86 +++++++++++-------- .../java/dataflow/internal/DataFlowImpl5.qll | 86 +++++++++++-------- .../dataflow/new/internal/DataFlowImpl.qll | 86 +++++++++++-------- .../dataflow/new/internal/DataFlowImpl2.qll | 86 +++++++++++-------- .../dataflow/new/internal/DataFlowImpl3.qll | 86 +++++++++++-------- .../dataflow/new/internal/DataFlowImpl4.qll | 86 +++++++++++-------- 22 files changed, 1144 insertions(+), 748 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll index 136c55f9e5f..d9f5acdd279 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll @@ -366,7 +366,7 @@ private module Stage1 { exists(Node mid, Node node, TypedContent tc | not fullBarrier(node, config) and useFieldFlow(config) and - fwdFlow(mid, config) and + fwdFlow(mid, _, config) and store(mid, tc, node, _) and c = tc.getContent() ) @@ -389,8 +389,8 @@ private module Stage1 { } pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, Node node, Configuration config) { - fwdFlowOut(call, node, true, config) + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) } /** @@ -584,21 +584,20 @@ private module Stage1 { revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) } - private predicate throughFlowNodeCand1(Node node, Configuration config) { + private predicate throughFlowNodeCand(Node node, Configuration config) { revFlow(node, true, config) and fwdFlow(node, true, config) and - not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) } /** Holds if flow may return from `callable`. */ pragma[nomagic] - private predicate returnFlowCallableNodeCand1( + private predicate returnFlowCallableNodeCand( DataFlowCallable callable, ReturnKindExt kind, Configuration config ) { exists(ReturnNodeExt ret | - throughFlowNodeCand1(ret, config) and + throughFlowNodeCand(ret, config) and callable = ret.getEnclosingCallable() and kind = ret.getKind() ) @@ -610,8 +609,8 @@ private module Stage1 { */ predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | - throughFlowNodeCand1(p, config) and - returnFlowCallableNodeCand1(c, kind, config) and + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and p.getEnclosingCallable() = c and exists(ap) and // we don't expect a parameter to return stored in itself @@ -803,7 +802,7 @@ private module Stage2 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -859,7 +858,7 @@ private module Stage2 { predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -878,7 +877,7 @@ private module Stage2 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -886,7 +885,7 @@ private module Stage2 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -970,13 +969,19 @@ private module Stage2 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -987,9 +992,9 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -1416,7 +1421,7 @@ private module Stage3 { CcNoCall() { this = false } } - Cc ccAny() { result = false } + Cc ccNone() { result = false } private class LocalCc = Unit; @@ -1481,7 +1486,7 @@ private module Stage3 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -1500,7 +1505,7 @@ private module Stage3 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -1508,7 +1513,7 @@ private module Stage3 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -1592,13 +1597,19 @@ private module Stage3 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -1609,9 +1620,9 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** @@ -2096,7 +2107,7 @@ private module Stage4 { class CcNoCall = CallContextNoCall; - Cc ccAny() { result instanceof CallContextAny } + Cc ccNone() { result instanceof CallContextAny } private class LocalCc = LocalCallContext; @@ -2108,7 +2119,7 @@ private module Stage4 { bindingset[call, c] private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccAny() + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() } bindingset[innercc, inner, call] @@ -2153,6 +2164,7 @@ private module Stage4 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { any() } + // Type checking is not necessary here as it has already been done in stage 3. bindingset[ap, contentType] private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } @@ -2180,7 +2192,7 @@ private module Stage4 { private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { flowCand(node, _, config) and config.isSource(node) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) or @@ -2199,7 +2211,7 @@ private module Stage4 { fwdFlow(mid, _, _, ap, config) and flowCand(node, _, unbind(config)) and jumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() ) or @@ -2207,7 +2219,7 @@ private module Stage4 { fwdFlow(mid, _, _, nil, config) and flowCand(node, _, unbind(config)) and additionalJumpStep(mid, node, config) and - cc = ccAny() and + cc = ccNone() and argAp = apNone() and ap = getApNil(node) ) @@ -2291,13 +2303,19 @@ private module Stage4 { ) } + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ pragma[nomagic] private predicate fwdFlowOut( - DataFlowCall call, Node node, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, node, allowsFieldFlow, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = ret.getEnclosingCallable() and checkCallContextReturn(innercc, inner, call) and ccOut = getCallContextReturn(inner, call) @@ -2308,9 +2326,9 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowOutFromArg( - DataFlowCall call, Node node, Ap argAp, Ap ap, Configuration config + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, node, any(CcCall ccc), _, apSome(argAp), ap, config) + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) } /** From 998e2de2c653e3f66cdc7d7b093f40068721e8b1 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Fri, 27 Nov 2020 12:23:38 +0100 Subject: [PATCH 95/97] Revert "Merge pull request #4653 from tamasvajk/feature/csharp9-relational-pattern" This reverts commit 5e75a4109ca09306d9b880365ee2b2499d2656b8, reversing changes made to c751c516bff7ee3d92939f30aae5b5dd37d2604f. --- .../2020-11-19-Relational-pattern.md | 3 - .../Entities/Expressions/Patterns/Pattern.cs | 3 - .../Expressions/Patterns/RecursivePattern.cs | 1 + .../Expressions/Patterns/RelationalPattern.cs | 29 - .../Kinds/ExprKind.cs | 6 +- .../ql/src/semmle/code/csharp/exprs/Expr.qll | 39 - csharp/ql/src/semmlecode.csharp.dbscheme | 6 - .../library-tests/csharp9/PrintAst.expected | 57 - .../csharp9/RelationalPattern.cs | 23 - .../csharp9/relationalPattern.expected | 5 - .../csharp9/relationalPattern.ql | 4 - .../old.dbscheme | 1890 ---------------- .../semmlecode.csharp.dbscheme | 1896 ----------------- .../upgrade.properties | 2 - 14 files changed, 2 insertions(+), 3962 deletions(-) delete mode 100644 csharp/change-notes/2020-11-19-Relational-pattern.md delete mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs delete mode 100644 csharp/ql/test/library-tests/csharp9/RelationalPattern.cs delete mode 100644 csharp/ql/test/library-tests/csharp9/relationalPattern.expected delete mode 100644 csharp/ql/test/library-tests/csharp9/relationalPattern.ql delete mode 100644 csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme delete mode 100644 csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme delete mode 100644 csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties diff --git a/csharp/change-notes/2020-11-19-Relational-pattern.md b/csharp/change-notes/2020-11-19-Relational-pattern.md deleted file mode 100644 index 77179884d12..00000000000 --- a/csharp/change-notes/2020-11-19-Relational-pattern.md +++ /dev/null @@ -1,3 +0,0 @@ -lgtm,codescanning -* The `RelationalPatternExpr` and its 4 sub class have been added to support C# 9 -relational `<`, `>`, `<=`, and `>=` patterns. diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs index 3888ac00112..7fe552ec8e9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs @@ -42,9 +42,6 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions case RecursivePatternSyntax recPattern: return new RecursivePattern(cx, recPattern, parent, child); - case RelationalPatternSyntax relPattern: - return new RelationalPattern(cx, relPattern, parent, child); - case VarPatternSyntax varPattern: switch (varPattern.Designation) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs index c6045f8cb14..997c622c818 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RecursivePattern.cs @@ -15,6 +15,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions /// The syntax node of the recursive pattern. /// The parent pattern/expression. /// The child index of this pattern. + /// If this pattern is in the top level of a case/is. In that case, the variable and type access are populated elsewhere. public RecursivePattern(Context cx, RecursivePatternSyntax syntax, IExpressionParentEntity parent, int child) : base(new ExpressionInfo(cx, Entities.NullType.Create(cx), cx.Create(syntax.GetLocation()), ExprKind.RECURSIVE_PATTERN, parent, child, false, null)) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs deleted file mode 100644 index 9dce7bf7c52..00000000000 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/RelationalPattern.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.CSharp; -using Semmle.Extraction.Kinds; -using Semmle.Extraction.Entities; - -namespace Semmle.Extraction.CSharp.Entities.Expressions -{ - internal class RelationalPattern : Expression - { - public RelationalPattern(Context cx, RelationalPatternSyntax syntax, IExpressionParentEntity parent, int child) : - base(new ExpressionInfo(cx, NullType.Create(cx), cx.Create(syntax.GetLocation()), GetKind(syntax.OperatorToken), parent, child, false, null)) - { - Expression.Create(cx, syntax.Expression, this, 0); - } - - private static ExprKind GetKind(SyntaxToken operatorToken) - { - return operatorToken.Kind() switch - { - SyntaxKind.LessThanEqualsToken => ExprKind.LE_PATTERN, - SyntaxKind.GreaterThanEqualsToken => ExprKind.GE_PATTERN, - SyntaxKind.LessThanToken => ExprKind.LT_PATTERN, - SyntaxKind.GreaterThanToken => ExprKind.GT_PATTERN, - _ => throw new InternalError(operatorToken.Parent, $"Relation pattern with operator token '{operatorToken.Kind()}' is not supported."), - }; - } - } -} diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs index c7c22b6789d..f46ed47ff5f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs @@ -115,10 +115,6 @@ namespace Semmle.Extraction.Kinds SWITCH_CASE = 118, ASSIGN_COALESCE = 119, SUPPRESS_NULLABLE_WARNING = 120, - NAMESPACE_ACCESS = 121, - LT_PATTERN = 122, - GT_PATTERN = 123, - LE_PATTERN = 124, - GE_PATTERN = 125, + NAMESPACE_ACCESS = 121 } } diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll index 0f7dcb64a40..b133b628732 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll @@ -342,45 +342,6 @@ class ConstantPatternExpr extends PatternExpr { override string getAPrimaryQlClass() { result = "ConstantPatternExpr" } } -/** A relational pattern, for example `>1` in `x is >1`. */ -class RelationalPatternExpr extends PatternExpr, @relational_pattern_expr { - /** Gets the name of the operator in this pattern. */ - string getOperator() { none() } - - /** Gets the expression of this relational pattern. */ - Expr getExpr() { result = this.getChild(0) } - - override string toString() { result = getOperator() + " ..." } -} - -/** A less-than pattern, for example `< 10` in `x is < 10`. */ -class LTPattern extends RelationalPatternExpr, @lt_pattern_expr { - override string getOperator() { result = "<" } - - override string getAPrimaryQlClass() { result = "LTPattern" } -} - -/** A greater-than pattern, for example `> 10` in `x is > 10`. */ -class GTPattern extends RelationalPatternExpr, @gt_pattern_expr { - override string getOperator() { result = ">" } - - override string getAPrimaryQlClass() { result = "GTPattern" } -} - -/** A less-than or equals pattern, for example `<= 10` in `x is <= 10`. */ -class LEPattern extends RelationalPatternExpr, @le_pattern_expr { - override string getOperator() { result = "<=" } - - override string getAPrimaryQlClass() { result = "LEPattern" } -} - -/** A greater-than or equals pattern, for example `>= 10` in `x is >= 10` */ -class GEPattern extends RelationalPatternExpr, @ge_pattern_expr { - override string getOperator() { result = ">=" } - - override string getAPrimaryQlClass() { result = "GEPattern" } -} - /** * A type pattern, for example `string` in `x is string`, `string s` in * `x is string s`, or `string _` in `x is string _`. diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme index 173895c0c82..eedef9359e1 100644 --- a/csharp/ql/src/semmlecode.csharp.dbscheme +++ b/csharp/ql/src/semmlecode.csharp.dbscheme @@ -998,17 +998,11 @@ case @expr.kind of | 119 = @assign_coalesce_expr | 120 = @suppress_nullable_warning_expr | 121 = @namespace_access_expr -/* C# 9.0 */ -| 122 = @lt_pattern_expr -| 123 = @gt_pattern_expr -| 124 = @le_pattern_expr -| 125 = @ge_pattern_expr ; @switch = @switch_stmt | @switch_expr; @case = @case_stmt | @switch_case_expr; @pattern_match = @case | @is_expr; -@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; @integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; @real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; diff --git a/csharp/ql/test/library-tests/csharp9/PrintAst.expected b/csharp/ql/test/library-tests/csharp9/PrintAst.expected index 4eecbab929c..42a0fad8d14 100644 --- a/csharp/ql/test/library-tests/csharp9/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp9/PrintAst.expected @@ -335,63 +335,6 @@ ParenthesizedPattern.cs: # 27| 0: [TypeAccessPatternExpr] access to type String # 27| 0: [TypeMention] string # 27| 2: [IntLiteral] 5 -RelationalPattern.cs: -# 3| [Class] RelationalPattern -# 5| 5: [Method] M1 -# 5| -1: [TypeMention] bool -#-----| 2: (Parameters) -# 5| 0: [Parameter] c -# 5| -1: [TypeMention] char -# 6| 4: [IsExpr] ... is ... -# 6| 0: [ParameterAccess] access to parameter c -# 6| 1: [GEPattern] >= ... -# 6| 0: [CharLiteral] a -# 7| 6: [Method] M2 -# 7| -1: [TypeMention] bool -#-----| 2: (Parameters) -# 7| 0: [Parameter] c -# 7| -1: [TypeMention] char -# 8| 4: [IsExpr] ... is ... -# 8| 0: [ParameterAccess] access to parameter c -# 8| 1: [GTPattern] > ... -# 8| 0: [CharLiteral] a -# 9| 7: [Method] M3 -# 9| -1: [TypeMention] bool -#-----| 2: (Parameters) -# 9| 0: [Parameter] c -# 9| -1: [TypeMention] char -# 10| 4: [IsExpr] ... is ... -# 10| 0: [ParameterAccess] access to parameter c -# 10| 1: [LEPattern] <= ... -# 10| 0: [CharLiteral] a -# 11| 8: [Method] M4 -# 11| -1: [TypeMention] bool -#-----| 2: (Parameters) -# 11| 0: [Parameter] c -# 11| -1: [TypeMention] char -# 12| 4: [IsExpr] ... is ... -# 12| 0: [ParameterAccess] access to parameter c -# 12| 1: [LTPattern] < ... -# 12| 0: [CharLiteral] a -# 14| 9: [Method] M5 -# 14| -1: [TypeMention] string -#-----| 2: (Parameters) -# 14| 0: [Parameter] i -# 14| -1: [TypeMention] int -# 15| 4: [BlockStmt] {...} -# 16| 0: [ReturnStmt] return ...; -# 16| 0: [SwitchExpr] ... switch { ... } -# 16| -1: [ParameterAccess] access to parameter i -# 18| 0: [SwitchCaseExpr] ... => ... -# 18| 0: [ConstantPatternExpr,IntLiteral] 1 -# 18| 2: [StringLiteral] "1" -# 19| 1: [SwitchCaseExpr] ... => ... -# 19| 0: [GTPattern] > ... -# 19| 0: [IntLiteral] 1 -# 19| 2: [StringLiteral] ">1" -# 20| 2: [SwitchCaseExpr] ... => ... -# 20| 0: [DiscardPatternExpr] _ -# 20| 2: [StringLiteral] "other" TargetType.cs: # 5| [Class] TargetType # 7| 5: [Method] M2 diff --git a/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs b/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs deleted file mode 100644 index d21f5044507..00000000000 --- a/csharp/ql/test/library-tests/csharp9/RelationalPattern.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -public class RelationalPattern -{ - public static bool M1(char c) => - c is >= 'a'; - public static bool M2(char c) => - c is > 'a'; - public static bool M3(char c) => - c is <= 'a'; - public static bool M4(char c) => - c is < 'a'; - - public static string M5(int i) - { - return i switch - { - 1 => "1", - >1 => ">1", - _ => "other" - }; - } -} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/csharp9/relationalPattern.expected b/csharp/ql/test/library-tests/csharp9/relationalPattern.expected deleted file mode 100644 index 43dca825928..00000000000 --- a/csharp/ql/test/library-tests/csharp9/relationalPattern.expected +++ /dev/null @@ -1,5 +0,0 @@ -| RelationalPattern.cs:6:14:6:19 | >= ... | >= | -| RelationalPattern.cs:8:14:8:18 | > ... | > | -| RelationalPattern.cs:10:14:10:19 | <= ... | <= | -| RelationalPattern.cs:12:14:12:18 | < ... | < | -| RelationalPattern.cs:19:13:19:14 | > ... | > | diff --git a/csharp/ql/test/library-tests/csharp9/relationalPattern.ql b/csharp/ql/test/library-tests/csharp9/relationalPattern.ql deleted file mode 100644 index 50618e5bb7d..00000000000 --- a/csharp/ql/test/library-tests/csharp9/relationalPattern.ql +++ /dev/null @@ -1,4 +0,0 @@ -import csharp - -from RelationalPatternExpr p -select p, p.getOperator() diff --git a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme deleted file mode 100644 index eedef9359e1..00000000000 --- a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/old.dbscheme +++ /dev/null @@ -1,1890 +0,0 @@ - -/** - * An invocation of the compiler. Note that more than one file may be - * compiled per invocation. For example, this command compiles three - * source files: - * - * csc f1.cs f2.cs f3.cs - * - * The `id` simply identifies the invocation, while `cwd` is the working - * directory from which the compiler was invoked. - */ -compilations( - unique int id : @compilation, - string cwd : string ref -); - -/** - * The arguments that were passed to the extractor for a compiler - * invocation. If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs - * - * then typically there will be rows for - * - * num | arg - * --- | --- - * 0 | --compiler - * 1 | *path to compiler* - * 2 | --cil - * 3 | f1.cs - * 4 | f2.cs - * 5 | f3.cs - */ -#keyset[id, num] -compilation_args( - int id : @compilation ref, - int num : int ref, - string arg : string ref -); - -/** - * The source files that are compiled by a compiler invocation. - * If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs - * - * then there will be rows for - * - * num | arg - * --- | --- - * 0 | f1.cs - * 1 | f2.cs - * 2 | f3.cs - */ -#keyset[id, num] -compilation_compiling_files( - int id : @compilation ref, - int num : int ref, - int file : @file ref -); - -/** - * The references used by a compiler invocation. - * If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll - * - * then there will be rows for - * - * num | arg - * --- | --- - * 0 | ref1.dll - * 1 | ref2.dll - * 2 | ref3.dll - */ -#keyset[id, num] -compilation_referencing_files( - int id : @compilation ref, - int num : int ref, - int file : @file ref -); - -/** - * The time taken by the extractor for a compiler invocation. - * - * For each file `num`, there will be rows for - * - * kind | seconds - * ---- | --- - * 1 | CPU seconds used by the extractor frontend - * 2 | Elapsed seconds during the extractor frontend - * 3 | CPU seconds used by the extractor backend - * 4 | Elapsed seconds during the extractor backend - */ -#keyset[id, num, kind] -compilation_time( - int id : @compilation ref, - int num : int ref, - /* kind: - 1 = frontend_cpu_seconds - 2 = frontend_elapsed_seconds - 3 = extractor_cpu_seconds - 4 = extractor_elapsed_seconds - */ - int kind : int ref, - float seconds : float ref -); - -/** - * An error or warning generated by the extractor. - * The diagnostic message `diagnostic` was generated during compiler - * invocation `compilation`, and is the `file_number_diagnostic_number`th - * message generated while extracting the `file_number`th file of that - * invocation. - */ -#keyset[compilation, file_number, file_number_diagnostic_number] -diagnostic_for( - unique int diagnostic : @diagnostic ref, - int compilation : @compilation ref, - int file_number : int ref, - int file_number_diagnostic_number : int ref -); - -diagnostics( - unique int id: @diagnostic, - int severity: int ref, - string error_tag: string ref, - string error_message: string ref, - string full_error_message: string ref, - int location: @location_default ref -); - -extractor_messages( - unique int id: @extractor_message, - int severity: int ref, - string origin : string ref, - string text : string ref, - string entity : string ref, - int location: @location_default ref, - string stack_trace : string ref -); - -/** - * If extraction was successful, then `cpu_seconds` and - * `elapsed_seconds` are the CPU time and elapsed time (respectively) - * that extraction took for compiler invocation `id`. - */ -compilation_finished( - unique int id : @compilation ref, - float cpu_seconds : float ref, - float elapsed_seconds : float ref -); - -/* - * External artifacts - */ - -externalDefects( - unique int id: @externalDefect, - string queryPath: string ref, - int location: @location ref, - string message: string ref, - float severity: float ref); - -externalMetrics( - unique int id: @externalMetric, - string queryPath: string ref, - int location: @location ref, - float value: float ref); - -externalData( - int id: @externalDataElement, - string path: string ref, - int column: int ref, - string value: string ref); - -snapshotDate( - unique date snapshotDate: date ref); - -sourceLocationPrefix( - string prefix: string ref); - -/* - * Duplicate code - */ - -duplicateCode( - unique int id: @duplication, - string relativePath: string ref, - int equivClass: int ref); - -similarCode( - unique int id: @similarity, - string relativePath: string ref, - int equivClass: int ref); - -@duplication_or_similarity = @duplication | @similarity - -tokens( - int id: @duplication_or_similarity ref, - int offset: int ref, - int beginLine: int ref, - int beginColumn: int ref, - int endLine: int ref, - int endColumn: int ref); - -/* - * C# dbscheme - */ - -/** ELEMENTS **/ - -@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration - | @using_directive | @type_parameter_constraints | @external_element - | @xmllocatable | @asp_element | @namespace; - -@declaration = @callable | @generic | @assignable | @namespace; - -@named_element = @namespace | @declaration; - -@declaration_with_accessors = @property | @indexer | @event; - -@assignable = @variable | @assignable_with_accessors | @event; - -@assignable_with_accessors = @property | @indexer; - -@external_element = @externalMetric | @externalDefect | @externalDataElement; - -@attributable = @assembly | @field | @parameter | @operator | @method | @constructor - | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors - | @local_function; - -/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ - -@location = @location_default | @assembly; - -locations_default( - unique int id: @location_default, - int file: @file ref, - int beginLine: int ref, - int beginColumn: int ref, - int endLine: int ref, - int endColumn: int ref); - -@sourceline = @file | @callable | @xmllocatable; - -numlines( - int element_id: @sourceline ref, - int num_lines: int ref, - int num_code: int ref, - int num_comment: int ref); - -assemblies( - unique int id: @assembly, - int file: @file ref, - string fullname: string ref, - string name: string ref, - string version: string ref); - -/* - fromSource(0) = unknown, - fromSource(1) = from source, - fromSource(2) = from library -*/ -files( - unique int id: @file, - string name: string ref, - string simple: string ref, - string ext: string ref, - int fromSource: int ref); - -folders( - unique int id: @folder, - string name: string ref, - string simple: string ref); - -@container = @folder | @file ; - -containerparent( - int parent: @container ref, - unique int child: @container ref); - -file_extraction_mode( - unique int file: @file ref, - int mode: int ref - /* 0 = normal, 1 = standalone extractor */ - ); - -/** NAMESPACES **/ - -@type_container = @namespace | @type; - -namespaces( - unique int id: @namespace, - string name: string ref); - -namespace_declarations( - unique int id: @namespace_declaration, - int namespace_id: @namespace ref); - -namespace_declaration_location( - unique int id: @namespace_declaration ref, - int loc: @location ref); - -parent_namespace( - unique int child_id: @type_container ref, - int namespace_id: @namespace ref); - -@declaration_or_directive = @namespace_declaration | @type | @using_directive; - -parent_namespace_declaration( - int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes - int namespace_id: @namespace_declaration ref); - -@using_directive = @using_namespace_directive | @using_static_directive; - -using_namespace_directives( - unique int id: @using_namespace_directive, - int namespace_id: @namespace ref); - -using_static_directives( - unique int id: @using_static_directive, - int type_id: @type_or_ref ref); - -using_directive_location( - unique int id: @using_directive ref, - int loc: @location ref); - -/** TYPES **/ - -types( - unique int id: @type, - int kind: int ref, - string name: string ref); - -case @type.kind of - 1 = @bool_type -| 2 = @char_type -| 3 = @decimal_type -| 4 = @sbyte_type -| 5 = @short_type -| 6 = @int_type -| 7 = @long_type -| 8 = @byte_type -| 9 = @ushort_type -| 10 = @uint_type -| 11 = @ulong_type -| 12 = @float_type -| 13 = @double_type -| 14 = @enum_type -| 15 = @struct_type -| 17 = @class_type -| 19 = @interface_type -| 20 = @delegate_type -| 21 = @null_type -| 22 = @type_parameter -| 23 = @pointer_type -| 24 = @nullable_type -| 25 = @array_type -| 26 = @void_type -| 27 = @int_ptr_type -| 28 = @uint_ptr_type -| 29 = @dynamic_type -| 30 = @arglist_type -| 31 = @unknown_type -| 32 = @tuple_type - ; - -@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; -@integral_type = @signed_integral_type | @unsigned_integral_type; -@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; -@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; -@floating_point_type = @float_type | @double_type; -@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type - | @uint_ptr_type | @tuple_type; -@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type - | @dynamic_type; -@value_or_ref_type = @value_type | @ref_type; - -typerefs( - unique int id: @typeref, - string name: string ref); - -typeref_type( - int id: @typeref ref, - unique int typeId: @type ref); - -@type_or_ref = @type | @typeref; - -array_element_type( - unique int array: @array_type ref, - int dimension: int ref, - int rank: int ref, - int element: @type_or_ref ref); - -nullable_underlying_type( - unique int nullable: @nullable_type ref, - int underlying: @type_or_ref ref); - -pointer_referent_type( - unique int pointer: @pointer_type ref, - int referent: @type_or_ref ref); - -enum_underlying_type( - unique int enum_id: @enum_type ref, - int underlying_type_id: @type_or_ref ref); - -delegate_return_type( - unique int delegate_id: @delegate_type ref, - int return_type_id: @type_or_ref ref); - -extend( - unique int sub: @type ref, - int super: @type_or_ref ref); - -@interface_or_ref = @interface_type | @typeref; - -implement( - int sub: @type ref, - int super: @type_or_ref ref); - -type_location( - int id: @type ref, - int loc: @location ref); - -tuple_underlying_type( - unique int tuple: @tuple_type ref, - int struct: @type_or_ref ref); - -#keyset[tuple, index] -tuple_element( - int tuple: @tuple_type ref, - int index: int ref, - unique int field: @field ref); - -attributes( - unique int id: @attribute, - int type_id: @type_or_ref ref, - int target: @attributable ref); - -attribute_location( - int id: @attribute ref, - int loc: @location ref); - -@type_mention_parent = @element | @type_mention; - -type_mention( - unique int id: @type_mention, - int type_id: @type_or_ref ref, - int parent: @type_mention_parent ref); - -type_mention_location( - unique int id: @type_mention ref, - int loc: @location ref); - -@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; - -/** - * A direct annotation on an entity, for example `string? x;`. - * - * Annotations: - * 2 = reftype is not annotated "!" - * 3 = reftype is annotated "?" - * 4 = readonly ref type / in parameter - * 5 = ref type parameter, return or local variable - * 6 = out parameter - * - * Note that the annotation depends on the element it annotates. - * @assignable: The annotation is on the type of the assignable, for example the variable type. - * @type_parameter: The annotation is on the reftype constraint - * @callable: The annotation is on the return type - * @array_type: The annotation is on the element type - */ -type_annotation(int id: @has_type_annotation ref, int annotation: int ref); - -nullability(unique int nullability: @nullability, int kind: int ref); - -case @nullability.kind of - 0 = @oblivious -| 1 = @not_annotated -| 2 = @annotated -; - -#keyset[parent, index] -nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) - -type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); - -/** - * The nullable flow state of an expression, as determined by Roslyn. - * 0 = none (default, not populated) - * 1 = not null - * 2 = maybe null - */ -expr_flowstate(unique int id: @expr ref, int state: int ref); - -/** GENERICS **/ - -@generic = @type | @method | @local_function; - -type_parameters( - unique int id: @type_parameter ref, - int index: int ref, - int generic_id: @generic ref, - int variance: int ref /* none = 0, out = 1, in = 2 */); - -#keyset[constructed_id, index] -type_arguments( - int id: @type_or_ref ref, - int index: int ref, - int constructed_id: @generic_or_ref ref); - -@generic_or_ref = @generic | @typeref; - -constructed_generic( - unique int constructed: @generic ref, - int generic: @generic_or_ref ref); - -type_parameter_constraints( - unique int id: @type_parameter_constraints, - int param_id: @type_parameter ref); - -type_parameter_constraints_location( - int id: @type_parameter_constraints ref, - int loc: @location ref); - -general_type_parameter_constraints( - int id: @type_parameter_constraints ref, - int kind: int ref /* class = 1, struct = 2, new = 3 */); - -specific_type_parameter_constraints( - int id: @type_parameter_constraints ref, - int base_id: @type_or_ref ref); - -specific_type_parameter_nullability( - int id: @type_parameter_constraints ref, - int base_id: @type_or_ref ref, - int nullability: @nullability ref); - -/** MODIFIERS */ - -@modifiable = @modifiable_direct | @event_accessor; - -@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; - -modifiers( - unique int id: @modifier, - string name: string ref); - -has_modifiers( - int id: @modifiable_direct ref, - int mod_id: @modifier ref); - -compiler_generated(unique int id: @modifiable_direct ref); - -/** MEMBERS **/ - -@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; - -@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; - -@virtualizable = @method | @property | @indexer | @event; - -exprorstmt_name( - unique int parent_id: @named_exprorstmt ref, - string name: string ref); - -nested_types( - unique int id: @type ref, - int declaring_type_id: @type ref, - int unbound_id: @type ref); - -properties( - unique int id: @property, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @property ref); - -property_location( - int id: @property ref, - int loc: @location ref); - -indexers( - unique int id: @indexer, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @indexer ref); - -indexer_location( - int id: @indexer ref, - int loc: @location ref); - -accessors( - unique int id: @accessor, - int kind: int ref, - string name: string ref, - int declaring_member_id: @member ref, - int unbound_id: @accessor ref); - -case @accessor.kind of - 1 = @getter -| 2 = @setter - ; - -accessor_location( - int id: @accessor ref, - int loc: @location ref); - -events( - unique int id: @event, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @event ref); - -event_location( - int id: @event ref, - int loc: @location ref); - -event_accessors( - unique int id: @event_accessor, - int kind: int ref, - string name: string ref, - int declaring_event_id: @event ref, - int unbound_id: @event_accessor ref); - -case @event_accessor.kind of - 1 = @add_event_accessor -| 2 = @remove_event_accessor - ; - -event_accessor_location( - int id: @event_accessor ref, - int loc: @location ref); - -operators( - unique int id: @operator, - string name: string ref, - string symbol: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @operator ref); - -operator_location( - int id: @operator ref, - int loc: @location ref); - -constant_value( - int id: @variable ref, - string value: string ref); - -/** CALLABLES **/ - -@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; - -@callable_accessor = @accessor | @event_accessor; - -methods( - unique int id: @method, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @method ref); - -method_location( - int id: @method ref, - int loc: @location ref); - -constructors( - unique int id: @constructor, - string name: string ref, - int declaring_type_id: @type ref, - int unbound_id: @constructor ref); - -constructor_location( - int id: @constructor ref, - int loc: @location ref); - -destructors( - unique int id: @destructor, - string name: string ref, - int declaring_type_id: @type ref, - int unbound_id: @destructor ref); - -destructor_location( - int id: @destructor ref, - int loc: @location ref); - -overrides( - int id: @callable ref, - int base_id: @callable ref); - -explicitly_implements( - int id: @member ref, - int interface_id: @interface_or_ref ref); - -local_functions( - unique int id: @local_function, - string name: string ref, - int return_type: @type ref, - int unbound_id: @local_function ref); - -local_function_stmts( - unique int fn: @local_function_stmt ref, - int stmt: @local_function ref); - -/** VARIABLES **/ - -@variable = @local_scope_variable | @field; - -@local_scope_variable = @local_variable | @parameter; - -fields( - unique int id: @field, - int kind: int ref, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @field ref); - -case @field.kind of - 1 = @addressable_field -| 2 = @constant - ; - -field_location( - int id: @field ref, - int loc: @location ref); - -localvars( - unique int id: @local_variable, - int kind: int ref, - string name: string ref, - int implicitly_typed: int ref /* 0 = no, 1 = yes */, - int type_id: @type_or_ref ref, - int parent_id: @local_var_decl_expr ref); - -case @local_variable.kind of - 1 = @addressable_local_variable -| 2 = @local_constant -| 3 = @local_variable_ref - ; - -localvar_location( - unique int id: @local_variable ref, - int loc: @location ref); - -@parameterizable = @callable | @delegate_type | @indexer; - -#keyset[name, parent_id] -#keyset[index, parent_id] -params( - unique int id: @parameter, - string name: string ref, - int type_id: @type_or_ref ref, - int index: int ref, - int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ - int parent_id: @parameterizable ref, - int unbound_id: @parameter ref); - -param_location( - int id: @parameter ref, - int loc: @location ref); - -/** STATEMENTS **/ - -@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; - -statements( - unique int id: @stmt, - int kind: int ref); - -#keyset[index, parent] -stmt_parent( - unique int stmt: @stmt ref, - int index: int ref, - int parent: @control_flow_element ref); - -@top_level_stmt_parent = @callable; - -// [index, parent] is not a keyset because the same parent may be compiled multiple times -stmt_parent_top_level( - unique int stmt: @stmt ref, - int index: int ref, - int parent: @top_level_stmt_parent ref); - -case @stmt.kind of - 1 = @block_stmt -| 2 = @expr_stmt -| 3 = @if_stmt -| 4 = @switch_stmt -| 5 = @while_stmt -| 6 = @do_stmt -| 7 = @for_stmt -| 8 = @foreach_stmt -| 9 = @break_stmt -| 10 = @continue_stmt -| 11 = @goto_stmt -| 12 = @goto_case_stmt -| 13 = @goto_default_stmt -| 14 = @throw_stmt -| 15 = @return_stmt -| 16 = @yield_stmt -| 17 = @try_stmt -| 18 = @checked_stmt -| 19 = @unchecked_stmt -| 20 = @lock_stmt -| 21 = @using_block_stmt -| 22 = @var_decl_stmt -| 23 = @const_decl_stmt -| 24 = @empty_stmt -| 25 = @unsafe_stmt -| 26 = @fixed_stmt -| 27 = @label_stmt -| 28 = @catch -| 29 = @case_stmt -| 30 = @local_function_stmt -| 31 = @using_decl_stmt - ; - -@using_stmt = @using_block_stmt | @using_decl_stmt; - -@labeled_stmt = @label_stmt | @case; - -@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; - -@cond_stmt = @if_stmt | @switch_stmt; - -@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; - -@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt - | @yield_stmt; - -@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; - - -stmt_location( - unique int id: @stmt ref, - int loc: @location ref); - -catch_type( - unique int catch_id: @catch ref, - int type_id: @type_or_ref ref, - int kind: int ref /* explicit = 1, implicit = 2 */); - -/** EXPRESSIONS **/ - -expressions( - unique int id: @expr, - int kind: int ref, - int type_id: @type_or_ref ref); - -#keyset[index, parent] -expr_parent( - unique int expr: @expr ref, - int index: int ref, - int parent: @control_flow_element ref); - -@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; - -@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; - -// [index, parent] is not a keyset because the same parent may be compiled multiple times -expr_parent_top_level( - unique int expr: @expr ref, - int index: int ref, - int parent: @top_level_exprorstmt_parent ref); - -case @expr.kind of -/* literal */ - 1 = @bool_literal_expr -| 2 = @char_literal_expr -| 3 = @decimal_literal_expr -| 4 = @int_literal_expr -| 5 = @long_literal_expr -| 6 = @uint_literal_expr -| 7 = @ulong_literal_expr -| 8 = @float_literal_expr -| 9 = @double_literal_expr -| 10 = @string_literal_expr -| 11 = @null_literal_expr -/* primary & unary */ -| 12 = @this_access_expr -| 13 = @base_access_expr -| 14 = @local_variable_access_expr -| 15 = @parameter_access_expr -| 16 = @field_access_expr -| 17 = @property_access_expr -| 18 = @method_access_expr -| 19 = @event_access_expr -| 20 = @indexer_access_expr -| 21 = @array_access_expr -| 22 = @type_access_expr -| 23 = @typeof_expr -| 24 = @method_invocation_expr -| 25 = @delegate_invocation_expr -| 26 = @operator_invocation_expr -| 27 = @cast_expr -| 28 = @object_creation_expr -| 29 = @explicit_delegate_creation_expr -| 30 = @implicit_delegate_creation_expr -| 31 = @array_creation_expr -| 32 = @default_expr -| 33 = @plus_expr -| 34 = @minus_expr -| 35 = @bit_not_expr -| 36 = @log_not_expr -| 37 = @post_incr_expr -| 38 = @post_decr_expr -| 39 = @pre_incr_expr -| 40 = @pre_decr_expr -/* multiplicative */ -| 41 = @mul_expr -| 42 = @div_expr -| 43 = @rem_expr -/* additive */ -| 44 = @add_expr -| 45 = @sub_expr -/* shift */ -| 46 = @lshift_expr -| 47 = @rshift_expr -/* relational */ -| 48 = @lt_expr -| 49 = @gt_expr -| 50 = @le_expr -| 51 = @ge_expr -/* equality */ -| 52 = @eq_expr -| 53 = @ne_expr -/* logical */ -| 54 = @bit_and_expr -| 55 = @bit_xor_expr -| 56 = @bit_or_expr -| 57 = @log_and_expr -| 58 = @log_or_expr -/* type testing */ -| 59 = @is_expr -| 60 = @as_expr -/* null coalescing */ -| 61 = @null_coalescing_expr -/* conditional */ -| 62 = @conditional_expr -/* assignment */ -| 63 = @simple_assign_expr -| 64 = @assign_add_expr -| 65 = @assign_sub_expr -| 66 = @assign_mul_expr -| 67 = @assign_div_expr -| 68 = @assign_rem_expr -| 69 = @assign_and_expr -| 70 = @assign_xor_expr -| 71 = @assign_or_expr -| 72 = @assign_lshift_expr -| 73 = @assign_rshift_expr -/* more */ -| 74 = @object_init_expr -| 75 = @collection_init_expr -| 76 = @array_init_expr -| 77 = @checked_expr -| 78 = @unchecked_expr -| 79 = @constructor_init_expr -| 80 = @add_event_expr -| 81 = @remove_event_expr -| 82 = @par_expr -| 83 = @local_var_decl_expr -| 84 = @lambda_expr -| 85 = @anonymous_method_expr -| 86 = @namespace_expr -/* dynamic */ -| 92 = @dynamic_element_access_expr -| 93 = @dynamic_member_access_expr -/* unsafe */ -| 100 = @pointer_indirection_expr -| 101 = @address_of_expr -| 102 = @sizeof_expr -/* async */ -| 103 = @await_expr -/* C# 6.0 */ -| 104 = @nameof_expr -| 105 = @interpolated_string_expr -| 106 = @unknown_expr -/* C# 7.0 */ -| 107 = @throw_expr -| 108 = @tuple_expr -| 109 = @local_function_invocation_expr -| 110 = @ref_expr -| 111 = @discard_expr -/* C# 8.0 */ -| 112 = @range_expr -| 113 = @index_expr -| 114 = @switch_expr -| 115 = @recursive_pattern_expr -| 116 = @property_pattern_expr -| 117 = @positional_pattern_expr -| 118 = @switch_case_expr -| 119 = @assign_coalesce_expr -| 120 = @suppress_nullable_warning_expr -| 121 = @namespace_access_expr -; - -@switch = @switch_stmt | @switch_expr; -@case = @case_stmt | @switch_case_expr; -@pattern_match = @case | @is_expr; - -@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; -@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; -@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr - | @string_literal_expr | @null_literal_expr; - -@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; -@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; -@assign_event_expr = @add_event_expr | @remove_event_expr; - -@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr - | @assign_rem_expr -@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr - | @assign_lshift_expr | @assign_rshift_expr; - -@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr - | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; -@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; -@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; - -@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; -@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; -@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; - -@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr - | @event_access_expr | @dynamic_member_access_expr; - -@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; - -@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; - -@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; -@incr_op_expr = @pre_incr_expr | @post_incr_expr; -@decr_op_expr = @pre_decr_expr | @post_decr_expr; -@mut_op_expr = @incr_op_expr | @decr_op_expr; -@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; -@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; - -@ternary_log_op_expr = @conditional_expr; -@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; -@un_log_op_expr = @log_not_expr; -@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; - -@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr - | @rshift_expr; -@un_bit_op_expr = @bit_not_expr; -@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; - -@equality_op_expr = @eq_expr | @ne_expr; -@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; -@comp_expr = @equality_op_expr | @rel_op_expr; - -@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; - -@ternary_op = @ternary_log_op_expr; -@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; -@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr - | @pointer_indirection_expr | @address_of_expr; - -@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; - -@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr - | @delegate_invocation_expr | @object_creation_expr | @call_access_expr - | @local_function_invocation_expr; - -@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; - -@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr - | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; - -@throw_element = @throw_expr | @throw_stmt; - -implicitly_typed_array_creation( - unique int id: @array_creation_expr ref); - -explicitly_sized_array_creation( - unique int id: @array_creation_expr ref); - -stackalloc_array_creation( - unique int id: @array_creation_expr ref); - -mutator_invocation_mode( - unique int id: @operator_invocation_expr ref, - int mode: int ref /* prefix = 1, postfix = 2*/); - -expr_compiler_generated( - unique int id: @expr ref); - -expr_value( - unique int id: @expr ref, - string value: string ref); - -expr_call( - unique int caller_id: @expr ref, - int target_id: @callable ref); - -expr_access( - unique int accesser_id: @access_expr ref, - int target_id: @accessible ref); - -@accessible = @method | @assignable | @local_function | @namespace; - -expr_location( - unique int id: @expr ref, - int loc: @location ref); - -dynamic_member_name( - unique int id: @late_bindable_expr ref, - string name: string ref); - -@qualifiable_expr = @member_access_expr - | @method_invocation_expr - | @element_access_expr; - -conditional_access( - unique int id: @qualifiable_expr ref); - -expr_argument( - unique int id: @expr ref, - int mode: int ref); - /* mode is the same as params: value = 0, ref = 1, out = 2 */ - -expr_argument_name( - unique int id: @expr ref, - string name: string ref); - -/** CONTROL/DATA FLOW **/ - -@control_flow_element = @stmt | @expr; - -/* XML Files */ - -xmlEncoding ( - unique int id: @file ref, - string encoding: string ref); - -xmlDTDs( - unique int id: @xmldtd, - string root: string ref, - string publicId: string ref, - string systemId: string ref, - int fileid: @file ref); - -xmlElements( - unique int id: @xmlelement, - string name: string ref, - int parentid: @xmlparent ref, - int idx: int ref, - int fileid: @file ref); - -xmlAttrs( - unique int id: @xmlattribute, - int elementid: @xmlelement ref, - string name: string ref, - string value: string ref, - int idx: int ref, - int fileid: @file ref); - -xmlNs( - int id: @xmlnamespace, - string prefixName: string ref, - string URI: string ref, - int fileid: @file ref); - -xmlHasNs( - int elementId: @xmlnamespaceable ref, - int nsId: @xmlnamespace ref, - int fileid: @file ref); - -xmlComments( - unique int id: @xmlcomment, - string text: string ref, - int parentid: @xmlparent ref, - int fileid: @file ref); - -xmlChars( - unique int id: @xmlcharacters, - string text: string ref, - int parentid: @xmlparent ref, - int idx: int ref, - int isCDATA: int ref, - int fileid: @file ref); - -@xmlparent = @file | @xmlelement; -@xmlnamespaceable = @xmlelement | @xmlattribute; - -xmllocations( - int xmlElement: @xmllocatable ref, - int location: @location_default ref); - -@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; - -/* Comments */ - -commentline( - unique int id: @commentline, - int kind: int ref, - string text: string ref, - string rawtext: string ref); - -case @commentline.kind of - 0 = @singlelinecomment -| 1 = @xmldoccomment -| 2 = @multilinecomment; - -commentline_location( - unique int id: @commentline ref, - int loc: @location ref); - -commentblock( - unique int id : @commentblock); - -commentblock_location( - unique int id: @commentblock ref, - int loc: @location ref); - -commentblock_binding( - int id: @commentblock ref, - int entity: @element ref, - int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ - -commentblock_child( - int id: @commentblock ref, - int commentline: @commentline ref, - int index: int ref); - -/* ASP.NET */ - -case @asp_element.kind of - 0=@asp_close_tag -| 1=@asp_code -| 2=@asp_comment -| 3=@asp_data_binding -| 4=@asp_directive -| 5=@asp_open_tag -| 6=@asp_quoted_string -| 7=@asp_text -| 8=@asp_xml_directive; - -@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; - -asp_elements( - unique int id: @asp_element, - int kind: int ref, - int loc: @location ref); - -asp_comment_server(unique int comment: @asp_comment ref); -asp_code_inline(unique int code: @asp_code ref); -asp_directive_attribute( - int directive: @asp_directive ref, - int index: int ref, - string name: string ref, - int value: @asp_quoted_string ref); -asp_directive_name( - unique int directive: @asp_directive ref, - string name: string ref); -asp_element_body( - unique int element: @asp_element ref, - string body: string ref); -asp_tag_attribute( - int tag: @asp_open_tag ref, - int index: int ref, - string name: string ref, - int attribute: @asp_attribute ref); -asp_tag_name( - unique int tag: @asp_open_tag ref, - string name: string ref); -asp_tag_isempty(int tag: @asp_open_tag ref); - -/* Common Intermediate Language - CIL */ - -case @cil_instruction.opcode of - 0 = @cil_nop -| 1 = @cil_break -| 2 = @cil_ldarg_0 -| 3 = @cil_ldarg_1 -| 4 = @cil_ldarg_2 -| 5 = @cil_ldarg_3 -| 6 = @cil_ldloc_0 -| 7 = @cil_ldloc_1 -| 8 = @cil_ldloc_2 -| 9 = @cil_ldloc_3 -| 10 = @cil_stloc_0 -| 11 = @cil_stloc_1 -| 12 = @cil_stloc_2 -| 13 = @cil_stloc_3 -| 14 = @cil_ldarg_s -| 15 = @cil_ldarga_s -| 16 = @cil_starg_s -| 17 = @cil_ldloc_s -| 18 = @cil_ldloca_s -| 19 = @cil_stloc_s -| 20 = @cil_ldnull -| 21 = @cil_ldc_i4_m1 -| 22 = @cil_ldc_i4_0 -| 23 = @cil_ldc_i4_1 -| 24 = @cil_ldc_i4_2 -| 25 = @cil_ldc_i4_3 -| 26 = @cil_ldc_i4_4 -| 27 = @cil_ldc_i4_5 -| 28 = @cil_ldc_i4_6 -| 29 = @cil_ldc_i4_7 -| 30 = @cil_ldc_i4_8 -| 31 = @cil_ldc_i4_s -| 32 = @cil_ldc_i4 -| 33 = @cil_ldc_i8 -| 34 = @cil_ldc_r4 -| 35 = @cil_ldc_r8 -| 37 = @cil_dup -| 38 = @cil_pop -| 39 = @cil_jmp -| 40 = @cil_call -| 41 = @cil_calli -| 42 = @cil_ret -| 43 = @cil_br_s -| 44 = @cil_brfalse_s -| 45 = @cil_brtrue_s -| 46 = @cil_beq_s -| 47 = @cil_bge_s -| 48 = @cil_bgt_s -| 49 = @cil_ble_s -| 50 = @cil_blt_s -| 51 = @cil_bne_un_s -| 52 = @cil_bge_un_s -| 53 = @cil_bgt_un_s -| 54 = @cil_ble_un_s -| 55 = @cil_blt_un_s -| 56 = @cil_br -| 57 = @cil_brfalse -| 58 = @cil_brtrue -| 59 = @cil_beq -| 60 = @cil_bge -| 61 = @cil_bgt -| 62 = @cil_ble -| 63 = @cil_blt -| 64 = @cil_bne_un -| 65 = @cil_bge_un -| 66 = @cil_bgt_un -| 67 = @cil_ble_un -| 68 = @cil_blt_un -| 69 = @cil_switch -| 70 = @cil_ldind_i1 -| 71 = @cil_ldind_u1 -| 72 = @cil_ldind_i2 -| 73 = @cil_ldind_u2 -| 74 = @cil_ldind_i4 -| 75 = @cil_ldind_u4 -| 76 = @cil_ldind_i8 -| 77 = @cil_ldind_i -| 78 = @cil_ldind_r4 -| 79 = @cil_ldind_r8 -| 80 = @cil_ldind_ref -| 81 = @cil_stind_ref -| 82 = @cil_stind_i1 -| 83 = @cil_stind_i2 -| 84 = @cil_stind_i4 -| 85 = @cil_stind_i8 -| 86 = @cil_stind_r4 -| 87 = @cil_stind_r8 -| 88 = @cil_add -| 89 = @cil_sub -| 90 = @cil_mul -| 91 = @cil_div -| 92 = @cil_div_un -| 93 = @cil_rem -| 94 = @cil_rem_un -| 95 = @cil_and -| 96 = @cil_or -| 97 = @cil_xor -| 98 = @cil_shl -| 99 = @cil_shr -| 100 = @cil_shr_un -| 101 = @cil_neg -| 102 = @cil_not -| 103 = @cil_conv_i1 -| 104 = @cil_conv_i2 -| 105 = @cil_conv_i4 -| 106 = @cil_conv_i8 -| 107 = @cil_conv_r4 -| 108 = @cil_conv_r8 -| 109 = @cil_conv_u4 -| 110 = @cil_conv_u8 -| 111 = @cil_callvirt -| 112 = @cil_cpobj -| 113 = @cil_ldobj -| 114 = @cil_ldstr -| 115 = @cil_newobj -| 116 = @cil_castclass -| 117 = @cil_isinst -| 118 = @cil_conv_r_un -| 121 = @cil_unbox -| 122 = @cil_throw -| 123 = @cil_ldfld -| 124 = @cil_ldflda -| 125 = @cil_stfld -| 126 = @cil_ldsfld -| 127 = @cil_ldsflda -| 128 = @cil_stsfld -| 129 = @cil_stobj -| 130 = @cil_conv_ovf_i1_un -| 131 = @cil_conv_ovf_i2_un -| 132 = @cil_conv_ovf_i4_un -| 133 = @cil_conv_ovf_i8_un -| 134 = @cil_conv_ovf_u1_un -| 135 = @cil_conv_ovf_u2_un -| 136 = @cil_conv_ovf_u4_un -| 137 = @cil_conv_ovf_u8_un -| 138 = @cil_conv_ovf_i_un -| 139 = @cil_conv_ovf_u_un -| 140 = @cil_box -| 141 = @cil_newarr -| 142 = @cil_ldlen -| 143 = @cil_ldelema -| 144 = @cil_ldelem_i1 -| 145 = @cil_ldelem_u1 -| 146 = @cil_ldelem_i2 -| 147 = @cil_ldelem_u2 -| 148 = @cil_ldelem_i4 -| 149 = @cil_ldelem_u4 -| 150 = @cil_ldelem_i8 -| 151 = @cil_ldelem_i -| 152 = @cil_ldelem_r4 -| 153 = @cil_ldelem_r8 -| 154 = @cil_ldelem_ref -| 155 = @cil_stelem_i -| 156 = @cil_stelem_i1 -| 157 = @cil_stelem_i2 -| 158 = @cil_stelem_i4 -| 159 = @cil_stelem_i8 -| 160 = @cil_stelem_r4 -| 161 = @cil_stelem_r8 -| 162 = @cil_stelem_ref -| 163 = @cil_ldelem -| 164 = @cil_stelem -| 165 = @cil_unbox_any -| 179 = @cil_conv_ovf_i1 -| 180 = @cil_conv_ovf_u1 -| 181 = @cil_conv_ovf_i2 -| 182 = @cil_conv_ovf_u2 -| 183 = @cil_conv_ovf_i4 -| 184 = @cil_conv_ovf_u4 -| 185 = @cil_conv_ovf_i8 -| 186 = @cil_conv_ovf_u8 -| 194 = @cil_refanyval -| 195 = @cil_ckinfinite -| 198 = @cil_mkrefany -| 208 = @cil_ldtoken -| 209 = @cil_conv_u2 -| 210 = @cil_conv_u1 -| 211 = @cil_conv_i -| 212 = @cil_conv_ovf_i -| 213 = @cil_conv_ovf_u -| 214 = @cil_add_ovf -| 215 = @cil_add_ovf_un -| 216 = @cil_mul_ovf -| 217 = @cil_mul_ovf_un -| 218 = @cil_sub_ovf -| 219 = @cil_sub_ovf_un -| 220 = @cil_endfinally -| 221 = @cil_leave -| 222 = @cil_leave_s -| 223 = @cil_stind_i -| 224 = @cil_conv_u -| 65024 = @cil_arglist -| 65025 = @cil_ceq -| 65026 = @cil_cgt -| 65027 = @cil_cgt_un -| 65028 = @cil_clt -| 65029 = @cil_clt_un -| 65030 = @cil_ldftn -| 65031 = @cil_ldvirtftn -| 65033 = @cil_ldarg -| 65034 = @cil_ldarga -| 65035 = @cil_starg -| 65036 = @cil_ldloc -| 65037 = @cil_ldloca -| 65038 = @cil_stloc -| 65039 = @cil_localloc -| 65041 = @cil_endfilter -| 65042 = @cil_unaligned -| 65043 = @cil_volatile -| 65044 = @cil_tail -| 65045 = @cil_initobj -| 65046 = @cil_constrained -| 65047 = @cil_cpblk -| 65048 = @cil_initblk -| 65050 = @cil_rethrow -| 65052 = @cil_sizeof -| 65053 = @cil_refanytype -| 65054 = @cil_readonly -; - -// CIL ignored instructions - -@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; - -// CIL local/parameter/field access - -@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; -@cil_starg_any = @cil_starg | @cil_starg_s; - -@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; -@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; - -@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; -@cil_stfld_any = @cil_stfld | @cil_stsfld; - -@cil_local_access = @cil_stloc_any | @cil_ldloc_any; -@cil_arg_access = @cil_starg_any | @cil_ldarg_any; -@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; -@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; - -@cil_stack_access = @cil_local_access | @cil_arg_access; -@cil_field_access = @cil_ldfld_any | @cil_stfld_any; - -@cil_access = @cil_read_access | @cil_write_access; - -// CIL constant/literal instructions - -@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; - -@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | - @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; - -@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; - -@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; - -// Control flow - -@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; -@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | - @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | - @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | - @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; -@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; -@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; -@cil_leave_any = @cil_leave | @cil_leave_s; -@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; - -// CIL call instructions - -@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; - -// CIL expression instructions - -@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | - @cil_newarr | @cil_ldtoken | @cil_sizeof | - @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; - -@cil_unary_expr = - @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| - @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | - @cil_ldind | @cil_unbox; - -@cil_conversion_operation = - @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | - @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | - @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | - @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | - @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | - @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | - @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | - @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | - @cil_conv_i | @cil_conv_u | @cil_conv_r_un; - -@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | - @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; - -@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | - @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; - -@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; - -@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; - -@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | - @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | - @cil_sub_ovf | @cil_sub_ovf_un; - -@cil_unary_bitwise_operation = @cil_not; - -@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; - -@cil_unary_arithmetic_operation = @cil_neg; - -@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; - -// Elements that retrieve an address of something -@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; - -// CIL array instructions - -@cil_read_array = - @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | - @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | - @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; - -@cil_write_array = @cil_stelem | @cil_stelem_ref | - @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | - @cil_stelem_r4 | @cil_stelem_r8; - -@cil_throw_any = @cil_throw | @cil_rethrow; - -#keyset[impl, index] -cil_instruction( - unique int id: @cil_instruction, - int opcode: int ref, - int index: int ref, - int impl: @cil_method_implementation ref); - -cil_jump( - unique int instruction: @cil_jump ref, - int target: @cil_instruction ref); - -cil_access( - unique int instruction: @cil_instruction ref, - int target: @cil_accessible ref); - -cil_value( - unique int instruction: @cil_literal ref, - string value: string ref); - -#keyset[instruction, index] -cil_switch( - int instruction: @cil_switch ref, - int index: int ref, - int target: @cil_instruction ref); - -cil_instruction_location( - unique int id: @cil_instruction ref, - int loc: @location ref); - -cil_type_location( - int id: @cil_type ref, - int loc: @location ref); - -cil_method_location( - int id: @cil_method ref, - int loc: @location ref); - -@cil_namespace = @namespace; - -@cil_type_container = @cil_type | @cil_namespace | @cil_method; - -case @cil_type.kind of - 0 = @cil_valueorreftype -| 1 = @cil_typeparameter -| 2 = @cil_array_type -| 3 = @cil_pointer_type -; - -cil_type( - unique int id: @cil_type, - string name: string ref, - int kind: int ref, - int parent: @cil_type_container ref, - int sourceDecl: @cil_type ref); - -cil_pointer_type( - unique int id: @cil_pointer_type ref, - int pointee: @cil_type ref); - -cil_array_type( - unique int id: @cil_array_type ref, - int element_type: @cil_type ref, - int rank: int ref); - -cil_method( - unique int id: @cil_method, - string name: string ref, - int parent: @cil_type ref, - int return_type: @cil_type ref); - -cil_method_source_declaration( - unique int method: @cil_method ref, - int source: @cil_method ref); - -cil_method_implementation( - unique int id: @cil_method_implementation, - int method: @cil_method ref, - int location: @assembly ref); - -cil_implements( - int id: @cil_method ref, - int decl: @cil_method ref); - -#keyset[parent, name] -cil_field( - unique int id: @cil_field, - int parent: @cil_type ref, - string name: string ref, - int field_type: @cil_type ref); - -@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; -@cil_named_element = @cil_declaration | @cil_namespace; -@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; -@cil_accessible = @cil_declaration; -@cil_variable = @cil_field | @cil_stack_variable; -@cil_stack_variable = @cil_local_variable | @cil_parameter; -@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; - -#keyset[method, index] -cil_parameter( - unique int id: @cil_parameter, - int method: @cil_method ref, - int index: int ref, - int param_type: @cil_type ref); - -cil_parameter_in(unique int id: @cil_parameter ref); -cil_parameter_out(unique int id: @cil_parameter ref); - -cil_setter(unique int prop: @cil_property ref, - int method: @cil_method ref); - -cil_getter(unique int prop: @cil_property ref, - int method: @cil_method ref); - -cil_adder(unique int event: @cil_event ref, - int method: @cil_method ref); - -cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); - -cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); - -cil_property( - unique int id: @cil_property, - int parent: @cil_type ref, - string name: string ref, - int property_type: @cil_type ref); - -#keyset[parent, name] -cil_event(unique int id: @cil_event, - int parent: @cil_type ref, - string name: string ref, - int event_type: @cil_type ref); - -#keyset[impl, index] -cil_local_variable( - unique int id: @cil_local_variable, - int impl: @cil_method_implementation ref, - int index: int ref, - int var_type: @cil_type ref); - -// CIL handlers (exception handlers etc). - -case @cil_handler.kind of - 0 = @cil_catch_handler -| 1 = @cil_filter_handler -| 2 = @cil_finally_handler -| 4 = @cil_fault_handler -; - -#keyset[impl, index] -cil_handler( - unique int id: @cil_handler, - int impl: @cil_method_implementation ref, - int index: int ref, - int kind: int ref, - int try_start: @cil_instruction ref, - int try_end: @cil_instruction ref, - int handler_start: @cil_instruction ref); - -cil_handler_filter( - unique int id: @cil_handler ref, - int filter_start: @cil_instruction ref); - -cil_handler_type( - unique int id: @cil_handler ref, - int catch_type: @cil_type ref); - -@cil_controlflow_node = @cil_entry_point | @cil_instruction; - -@cil_entry_point = @cil_method_implementation | @cil_handler; - -@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; - -cil_method_stack_size( - unique int method: @cil_method_implementation ref, - int size: int ref); - -// CIL modifiers - -cil_public(int id: @cil_member ref); -cil_private(int id: @cil_member ref); -cil_protected(int id: @cil_member ref); -cil_internal(int id: @cil_member ref); -cil_static(int id: @cil_member ref); -cil_sealed(int id: @cil_member ref); -cil_virtual(int id: @cil_method ref); -cil_abstract(int id: @cil_member ref); -cil_class(int id: @cil_type ref); -cil_interface(int id: @cil_type ref); -cil_security(int id: @cil_member ref); -cil_requiresecobject(int id: @cil_method ref); -cil_specialname(int id: @cil_method ref); -cil_newslot(int id: @cil_method ref); - -cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); -cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); - -#keyset[unbound, index] -cil_type_parameter( - int unbound: @cil_member ref, - int index: int ref, - int param: @cil_typeparameter ref); - -#keyset[bound, index] -cil_type_argument( - int bound: @cil_member ref, - int index: int ref, - int t: @cil_type ref); - -// CIL type parameter constraints - -cil_typeparam_covariant(int tp: @cil_typeparameter ref); -cil_typeparam_contravariant(int tp: @cil_typeparameter ref); -cil_typeparam_class(int tp: @cil_typeparameter ref); -cil_typeparam_struct(int tp: @cil_typeparameter ref); -cil_typeparam_new(int tp: @cil_typeparameter ref); -cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); - -// CIL attributes - -cil_attribute( - unique int attributeid: @cil_attribute, - int element: @cil_declaration ref, - int constructor: @cil_method ref); - -#keyset[attribute_id, param] -cil_attribute_named_argument( - int attribute_id: @cil_attribute ref, - string param: string ref, - string value: string ref); - -#keyset[attribute_id, index] -cil_attribute_positional_argument( - int attribute_id: @cil_attribute ref, - int index: int ref, - string value: string ref); - - -// Common .Net data model covering both C# and CIL - -// Common elements -@dotnet_element = @element | @cil_element; -@dotnet_named_element = @named_element | @cil_named_element; -@dotnet_callable = @callable | @cil_method; -@dotnet_variable = @variable | @cil_variable; -@dotnet_field = @field | @cil_field; -@dotnet_parameter = @parameter | @cil_parameter; -@dotnet_declaration = @declaration | @cil_declaration; -@dotnet_member = @member | @cil_member; -@dotnet_event = @event | @cil_event; -@dotnet_property = @property | @cil_property | @indexer; - -// Common types -@dotnet_type = @type | @cil_type; -@dotnet_call = @call | @cil_call_any; -@dotnet_throw = @throw_element | @cil_throw_any; -@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; -@dotnet_typeparameter = @type_parameter | @cil_typeparameter; -@dotnet_array_type = @array_type | @cil_array_type; -@dotnet_pointer_type = @pointer_type | @cil_pointer_type; -@dotnet_type_parameter = @type_parameter | @cil_typeparameter; -@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; - -// Attributes -@dotnet_attribute = @attribute | @cil_attribute; - -// Expressions -@dotnet_expr = @expr | @cil_expr; - -// Literals -@dotnet_literal = @literal_expr | @cil_literal; -@dotnet_string_literal = @string_literal_expr | @cil_ldstr; -@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; -@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; -@dotnet_null_literal = @null_literal_expr | @cil_ldnull; - -@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | - @callable | @value_or_ref_type | @void_type; - -#keyset[entity, location] -metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme deleted file mode 100644 index 173895c0c82..00000000000 --- a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/semmlecode.csharp.dbscheme +++ /dev/null @@ -1,1896 +0,0 @@ - -/** - * An invocation of the compiler. Note that more than one file may be - * compiled per invocation. For example, this command compiles three - * source files: - * - * csc f1.cs f2.cs f3.cs - * - * The `id` simply identifies the invocation, while `cwd` is the working - * directory from which the compiler was invoked. - */ -compilations( - unique int id : @compilation, - string cwd : string ref -); - -/** - * The arguments that were passed to the extractor for a compiler - * invocation. If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs - * - * then typically there will be rows for - * - * num | arg - * --- | --- - * 0 | --compiler - * 1 | *path to compiler* - * 2 | --cil - * 3 | f1.cs - * 4 | f2.cs - * 5 | f3.cs - */ -#keyset[id, num] -compilation_args( - int id : @compilation ref, - int num : int ref, - string arg : string ref -); - -/** - * The source files that are compiled by a compiler invocation. - * If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs - * - * then there will be rows for - * - * num | arg - * --- | --- - * 0 | f1.cs - * 1 | f2.cs - * 2 | f3.cs - */ -#keyset[id, num] -compilation_compiling_files( - int id : @compilation ref, - int num : int ref, - int file : @file ref -); - -/** - * The references used by a compiler invocation. - * If `id` is for the compiler invocation - * - * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll - * - * then there will be rows for - * - * num | arg - * --- | --- - * 0 | ref1.dll - * 1 | ref2.dll - * 2 | ref3.dll - */ -#keyset[id, num] -compilation_referencing_files( - int id : @compilation ref, - int num : int ref, - int file : @file ref -); - -/** - * The time taken by the extractor for a compiler invocation. - * - * For each file `num`, there will be rows for - * - * kind | seconds - * ---- | --- - * 1 | CPU seconds used by the extractor frontend - * 2 | Elapsed seconds during the extractor frontend - * 3 | CPU seconds used by the extractor backend - * 4 | Elapsed seconds during the extractor backend - */ -#keyset[id, num, kind] -compilation_time( - int id : @compilation ref, - int num : int ref, - /* kind: - 1 = frontend_cpu_seconds - 2 = frontend_elapsed_seconds - 3 = extractor_cpu_seconds - 4 = extractor_elapsed_seconds - */ - int kind : int ref, - float seconds : float ref -); - -/** - * An error or warning generated by the extractor. - * The diagnostic message `diagnostic` was generated during compiler - * invocation `compilation`, and is the `file_number_diagnostic_number`th - * message generated while extracting the `file_number`th file of that - * invocation. - */ -#keyset[compilation, file_number, file_number_diagnostic_number] -diagnostic_for( - unique int diagnostic : @diagnostic ref, - int compilation : @compilation ref, - int file_number : int ref, - int file_number_diagnostic_number : int ref -); - -diagnostics( - unique int id: @diagnostic, - int severity: int ref, - string error_tag: string ref, - string error_message: string ref, - string full_error_message: string ref, - int location: @location_default ref -); - -extractor_messages( - unique int id: @extractor_message, - int severity: int ref, - string origin : string ref, - string text : string ref, - string entity : string ref, - int location: @location_default ref, - string stack_trace : string ref -); - -/** - * If extraction was successful, then `cpu_seconds` and - * `elapsed_seconds` are the CPU time and elapsed time (respectively) - * that extraction took for compiler invocation `id`. - */ -compilation_finished( - unique int id : @compilation ref, - float cpu_seconds : float ref, - float elapsed_seconds : float ref -); - -/* - * External artifacts - */ - -externalDefects( - unique int id: @externalDefect, - string queryPath: string ref, - int location: @location ref, - string message: string ref, - float severity: float ref); - -externalMetrics( - unique int id: @externalMetric, - string queryPath: string ref, - int location: @location ref, - float value: float ref); - -externalData( - int id: @externalDataElement, - string path: string ref, - int column: int ref, - string value: string ref); - -snapshotDate( - unique date snapshotDate: date ref); - -sourceLocationPrefix( - string prefix: string ref); - -/* - * Duplicate code - */ - -duplicateCode( - unique int id: @duplication, - string relativePath: string ref, - int equivClass: int ref); - -similarCode( - unique int id: @similarity, - string relativePath: string ref, - int equivClass: int ref); - -@duplication_or_similarity = @duplication | @similarity - -tokens( - int id: @duplication_or_similarity ref, - int offset: int ref, - int beginLine: int ref, - int beginColumn: int ref, - int endLine: int ref, - int endColumn: int ref); - -/* - * C# dbscheme - */ - -/** ELEMENTS **/ - -@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration - | @using_directive | @type_parameter_constraints | @external_element - | @xmllocatable | @asp_element | @namespace; - -@declaration = @callable | @generic | @assignable | @namespace; - -@named_element = @namespace | @declaration; - -@declaration_with_accessors = @property | @indexer | @event; - -@assignable = @variable | @assignable_with_accessors | @event; - -@assignable_with_accessors = @property | @indexer; - -@external_element = @externalMetric | @externalDefect | @externalDataElement; - -@attributable = @assembly | @field | @parameter | @operator | @method | @constructor - | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors - | @local_function; - -/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ - -@location = @location_default | @assembly; - -locations_default( - unique int id: @location_default, - int file: @file ref, - int beginLine: int ref, - int beginColumn: int ref, - int endLine: int ref, - int endColumn: int ref); - -@sourceline = @file | @callable | @xmllocatable; - -numlines( - int element_id: @sourceline ref, - int num_lines: int ref, - int num_code: int ref, - int num_comment: int ref); - -assemblies( - unique int id: @assembly, - int file: @file ref, - string fullname: string ref, - string name: string ref, - string version: string ref); - -/* - fromSource(0) = unknown, - fromSource(1) = from source, - fromSource(2) = from library -*/ -files( - unique int id: @file, - string name: string ref, - string simple: string ref, - string ext: string ref, - int fromSource: int ref); - -folders( - unique int id: @folder, - string name: string ref, - string simple: string ref); - -@container = @folder | @file ; - -containerparent( - int parent: @container ref, - unique int child: @container ref); - -file_extraction_mode( - unique int file: @file ref, - int mode: int ref - /* 0 = normal, 1 = standalone extractor */ - ); - -/** NAMESPACES **/ - -@type_container = @namespace | @type; - -namespaces( - unique int id: @namespace, - string name: string ref); - -namespace_declarations( - unique int id: @namespace_declaration, - int namespace_id: @namespace ref); - -namespace_declaration_location( - unique int id: @namespace_declaration ref, - int loc: @location ref); - -parent_namespace( - unique int child_id: @type_container ref, - int namespace_id: @namespace ref); - -@declaration_or_directive = @namespace_declaration | @type | @using_directive; - -parent_namespace_declaration( - int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes - int namespace_id: @namespace_declaration ref); - -@using_directive = @using_namespace_directive | @using_static_directive; - -using_namespace_directives( - unique int id: @using_namespace_directive, - int namespace_id: @namespace ref); - -using_static_directives( - unique int id: @using_static_directive, - int type_id: @type_or_ref ref); - -using_directive_location( - unique int id: @using_directive ref, - int loc: @location ref); - -/** TYPES **/ - -types( - unique int id: @type, - int kind: int ref, - string name: string ref); - -case @type.kind of - 1 = @bool_type -| 2 = @char_type -| 3 = @decimal_type -| 4 = @sbyte_type -| 5 = @short_type -| 6 = @int_type -| 7 = @long_type -| 8 = @byte_type -| 9 = @ushort_type -| 10 = @uint_type -| 11 = @ulong_type -| 12 = @float_type -| 13 = @double_type -| 14 = @enum_type -| 15 = @struct_type -| 17 = @class_type -| 19 = @interface_type -| 20 = @delegate_type -| 21 = @null_type -| 22 = @type_parameter -| 23 = @pointer_type -| 24 = @nullable_type -| 25 = @array_type -| 26 = @void_type -| 27 = @int_ptr_type -| 28 = @uint_ptr_type -| 29 = @dynamic_type -| 30 = @arglist_type -| 31 = @unknown_type -| 32 = @tuple_type - ; - -@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; -@integral_type = @signed_integral_type | @unsigned_integral_type; -@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; -@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; -@floating_point_type = @float_type | @double_type; -@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type - | @uint_ptr_type | @tuple_type; -@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type - | @dynamic_type; -@value_or_ref_type = @value_type | @ref_type; - -typerefs( - unique int id: @typeref, - string name: string ref); - -typeref_type( - int id: @typeref ref, - unique int typeId: @type ref); - -@type_or_ref = @type | @typeref; - -array_element_type( - unique int array: @array_type ref, - int dimension: int ref, - int rank: int ref, - int element: @type_or_ref ref); - -nullable_underlying_type( - unique int nullable: @nullable_type ref, - int underlying: @type_or_ref ref); - -pointer_referent_type( - unique int pointer: @pointer_type ref, - int referent: @type_or_ref ref); - -enum_underlying_type( - unique int enum_id: @enum_type ref, - int underlying_type_id: @type_or_ref ref); - -delegate_return_type( - unique int delegate_id: @delegate_type ref, - int return_type_id: @type_or_ref ref); - -extend( - unique int sub: @type ref, - int super: @type_or_ref ref); - -@interface_or_ref = @interface_type | @typeref; - -implement( - int sub: @type ref, - int super: @type_or_ref ref); - -type_location( - int id: @type ref, - int loc: @location ref); - -tuple_underlying_type( - unique int tuple: @tuple_type ref, - int struct: @type_or_ref ref); - -#keyset[tuple, index] -tuple_element( - int tuple: @tuple_type ref, - int index: int ref, - unique int field: @field ref); - -attributes( - unique int id: @attribute, - int type_id: @type_or_ref ref, - int target: @attributable ref); - -attribute_location( - int id: @attribute ref, - int loc: @location ref); - -@type_mention_parent = @element | @type_mention; - -type_mention( - unique int id: @type_mention, - int type_id: @type_or_ref ref, - int parent: @type_mention_parent ref); - -type_mention_location( - unique int id: @type_mention ref, - int loc: @location ref); - -@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic; - -/** - * A direct annotation on an entity, for example `string? x;`. - * - * Annotations: - * 2 = reftype is not annotated "!" - * 3 = reftype is annotated "?" - * 4 = readonly ref type / in parameter - * 5 = ref type parameter, return or local variable - * 6 = out parameter - * - * Note that the annotation depends on the element it annotates. - * @assignable: The annotation is on the type of the assignable, for example the variable type. - * @type_parameter: The annotation is on the reftype constraint - * @callable: The annotation is on the return type - * @array_type: The annotation is on the element type - */ -type_annotation(int id: @has_type_annotation ref, int annotation: int ref); - -nullability(unique int nullability: @nullability, int kind: int ref); - -case @nullability.kind of - 0 = @oblivious -| 1 = @not_annotated -| 2 = @annotated -; - -#keyset[parent, index] -nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) - -type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); - -/** - * The nullable flow state of an expression, as determined by Roslyn. - * 0 = none (default, not populated) - * 1 = not null - * 2 = maybe null - */ -expr_flowstate(unique int id: @expr ref, int state: int ref); - -/** GENERICS **/ - -@generic = @type | @method | @local_function; - -type_parameters( - unique int id: @type_parameter ref, - int index: int ref, - int generic_id: @generic ref, - int variance: int ref /* none = 0, out = 1, in = 2 */); - -#keyset[constructed_id, index] -type_arguments( - int id: @type_or_ref ref, - int index: int ref, - int constructed_id: @generic_or_ref ref); - -@generic_or_ref = @generic | @typeref; - -constructed_generic( - unique int constructed: @generic ref, - int generic: @generic_or_ref ref); - -type_parameter_constraints( - unique int id: @type_parameter_constraints, - int param_id: @type_parameter ref); - -type_parameter_constraints_location( - int id: @type_parameter_constraints ref, - int loc: @location ref); - -general_type_parameter_constraints( - int id: @type_parameter_constraints ref, - int kind: int ref /* class = 1, struct = 2, new = 3 */); - -specific_type_parameter_constraints( - int id: @type_parameter_constraints ref, - int base_id: @type_or_ref ref); - -specific_type_parameter_nullability( - int id: @type_parameter_constraints ref, - int base_id: @type_or_ref ref, - int nullability: @nullability ref); - -/** MODIFIERS */ - -@modifiable = @modifiable_direct | @event_accessor; - -@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; - -modifiers( - unique int id: @modifier, - string name: string ref); - -has_modifiers( - int id: @modifiable_direct ref, - int mod_id: @modifier ref); - -compiler_generated(unique int id: @modifiable_direct ref); - -/** MEMBERS **/ - -@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; - -@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; - -@virtualizable = @method | @property | @indexer | @event; - -exprorstmt_name( - unique int parent_id: @named_exprorstmt ref, - string name: string ref); - -nested_types( - unique int id: @type ref, - int declaring_type_id: @type ref, - int unbound_id: @type ref); - -properties( - unique int id: @property, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @property ref); - -property_location( - int id: @property ref, - int loc: @location ref); - -indexers( - unique int id: @indexer, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @indexer ref); - -indexer_location( - int id: @indexer ref, - int loc: @location ref); - -accessors( - unique int id: @accessor, - int kind: int ref, - string name: string ref, - int declaring_member_id: @member ref, - int unbound_id: @accessor ref); - -case @accessor.kind of - 1 = @getter -| 2 = @setter - ; - -accessor_location( - int id: @accessor ref, - int loc: @location ref); - -events( - unique int id: @event, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @event ref); - -event_location( - int id: @event ref, - int loc: @location ref); - -event_accessors( - unique int id: @event_accessor, - int kind: int ref, - string name: string ref, - int declaring_event_id: @event ref, - int unbound_id: @event_accessor ref); - -case @event_accessor.kind of - 1 = @add_event_accessor -| 2 = @remove_event_accessor - ; - -event_accessor_location( - int id: @event_accessor ref, - int loc: @location ref); - -operators( - unique int id: @operator, - string name: string ref, - string symbol: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @operator ref); - -operator_location( - int id: @operator ref, - int loc: @location ref); - -constant_value( - int id: @variable ref, - string value: string ref); - -/** CALLABLES **/ - -@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; - -@callable_accessor = @accessor | @event_accessor; - -methods( - unique int id: @method, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @method ref); - -method_location( - int id: @method ref, - int loc: @location ref); - -constructors( - unique int id: @constructor, - string name: string ref, - int declaring_type_id: @type ref, - int unbound_id: @constructor ref); - -constructor_location( - int id: @constructor ref, - int loc: @location ref); - -destructors( - unique int id: @destructor, - string name: string ref, - int declaring_type_id: @type ref, - int unbound_id: @destructor ref); - -destructor_location( - int id: @destructor ref, - int loc: @location ref); - -overrides( - int id: @callable ref, - int base_id: @callable ref); - -explicitly_implements( - int id: @member ref, - int interface_id: @interface_or_ref ref); - -local_functions( - unique int id: @local_function, - string name: string ref, - int return_type: @type ref, - int unbound_id: @local_function ref); - -local_function_stmts( - unique int fn: @local_function_stmt ref, - int stmt: @local_function ref); - -/** VARIABLES **/ - -@variable = @local_scope_variable | @field; - -@local_scope_variable = @local_variable | @parameter; - -fields( - unique int id: @field, - int kind: int ref, - string name: string ref, - int declaring_type_id: @type ref, - int type_id: @type_or_ref ref, - int unbound_id: @field ref); - -case @field.kind of - 1 = @addressable_field -| 2 = @constant - ; - -field_location( - int id: @field ref, - int loc: @location ref); - -localvars( - unique int id: @local_variable, - int kind: int ref, - string name: string ref, - int implicitly_typed: int ref /* 0 = no, 1 = yes */, - int type_id: @type_or_ref ref, - int parent_id: @local_var_decl_expr ref); - -case @local_variable.kind of - 1 = @addressable_local_variable -| 2 = @local_constant -| 3 = @local_variable_ref - ; - -localvar_location( - unique int id: @local_variable ref, - int loc: @location ref); - -@parameterizable = @callable | @delegate_type | @indexer; - -#keyset[name, parent_id] -#keyset[index, parent_id] -params( - unique int id: @parameter, - string name: string ref, - int type_id: @type_or_ref ref, - int index: int ref, - int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ - int parent_id: @parameterizable ref, - int unbound_id: @parameter ref); - -param_location( - int id: @parameter ref, - int loc: @location ref); - -/** STATEMENTS **/ - -@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; - -statements( - unique int id: @stmt, - int kind: int ref); - -#keyset[index, parent] -stmt_parent( - unique int stmt: @stmt ref, - int index: int ref, - int parent: @control_flow_element ref); - -@top_level_stmt_parent = @callable; - -// [index, parent] is not a keyset because the same parent may be compiled multiple times -stmt_parent_top_level( - unique int stmt: @stmt ref, - int index: int ref, - int parent: @top_level_stmt_parent ref); - -case @stmt.kind of - 1 = @block_stmt -| 2 = @expr_stmt -| 3 = @if_stmt -| 4 = @switch_stmt -| 5 = @while_stmt -| 6 = @do_stmt -| 7 = @for_stmt -| 8 = @foreach_stmt -| 9 = @break_stmt -| 10 = @continue_stmt -| 11 = @goto_stmt -| 12 = @goto_case_stmt -| 13 = @goto_default_stmt -| 14 = @throw_stmt -| 15 = @return_stmt -| 16 = @yield_stmt -| 17 = @try_stmt -| 18 = @checked_stmt -| 19 = @unchecked_stmt -| 20 = @lock_stmt -| 21 = @using_block_stmt -| 22 = @var_decl_stmt -| 23 = @const_decl_stmt -| 24 = @empty_stmt -| 25 = @unsafe_stmt -| 26 = @fixed_stmt -| 27 = @label_stmt -| 28 = @catch -| 29 = @case_stmt -| 30 = @local_function_stmt -| 31 = @using_decl_stmt - ; - -@using_stmt = @using_block_stmt | @using_decl_stmt; - -@labeled_stmt = @label_stmt | @case; - -@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; - -@cond_stmt = @if_stmt | @switch_stmt; - -@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; - -@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt - | @yield_stmt; - -@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; - - -stmt_location( - unique int id: @stmt ref, - int loc: @location ref); - -catch_type( - unique int catch_id: @catch ref, - int type_id: @type_or_ref ref, - int kind: int ref /* explicit = 1, implicit = 2 */); - -/** EXPRESSIONS **/ - -expressions( - unique int id: @expr, - int kind: int ref, - int type_id: @type_or_ref ref); - -#keyset[index, parent] -expr_parent( - unique int expr: @expr ref, - int index: int ref, - int parent: @control_flow_element ref); - -@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter; - -@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; - -// [index, parent] is not a keyset because the same parent may be compiled multiple times -expr_parent_top_level( - unique int expr: @expr ref, - int index: int ref, - int parent: @top_level_exprorstmt_parent ref); - -case @expr.kind of -/* literal */ - 1 = @bool_literal_expr -| 2 = @char_literal_expr -| 3 = @decimal_literal_expr -| 4 = @int_literal_expr -| 5 = @long_literal_expr -| 6 = @uint_literal_expr -| 7 = @ulong_literal_expr -| 8 = @float_literal_expr -| 9 = @double_literal_expr -| 10 = @string_literal_expr -| 11 = @null_literal_expr -/* primary & unary */ -| 12 = @this_access_expr -| 13 = @base_access_expr -| 14 = @local_variable_access_expr -| 15 = @parameter_access_expr -| 16 = @field_access_expr -| 17 = @property_access_expr -| 18 = @method_access_expr -| 19 = @event_access_expr -| 20 = @indexer_access_expr -| 21 = @array_access_expr -| 22 = @type_access_expr -| 23 = @typeof_expr -| 24 = @method_invocation_expr -| 25 = @delegate_invocation_expr -| 26 = @operator_invocation_expr -| 27 = @cast_expr -| 28 = @object_creation_expr -| 29 = @explicit_delegate_creation_expr -| 30 = @implicit_delegate_creation_expr -| 31 = @array_creation_expr -| 32 = @default_expr -| 33 = @plus_expr -| 34 = @minus_expr -| 35 = @bit_not_expr -| 36 = @log_not_expr -| 37 = @post_incr_expr -| 38 = @post_decr_expr -| 39 = @pre_incr_expr -| 40 = @pre_decr_expr -/* multiplicative */ -| 41 = @mul_expr -| 42 = @div_expr -| 43 = @rem_expr -/* additive */ -| 44 = @add_expr -| 45 = @sub_expr -/* shift */ -| 46 = @lshift_expr -| 47 = @rshift_expr -/* relational */ -| 48 = @lt_expr -| 49 = @gt_expr -| 50 = @le_expr -| 51 = @ge_expr -/* equality */ -| 52 = @eq_expr -| 53 = @ne_expr -/* logical */ -| 54 = @bit_and_expr -| 55 = @bit_xor_expr -| 56 = @bit_or_expr -| 57 = @log_and_expr -| 58 = @log_or_expr -/* type testing */ -| 59 = @is_expr -| 60 = @as_expr -/* null coalescing */ -| 61 = @null_coalescing_expr -/* conditional */ -| 62 = @conditional_expr -/* assignment */ -| 63 = @simple_assign_expr -| 64 = @assign_add_expr -| 65 = @assign_sub_expr -| 66 = @assign_mul_expr -| 67 = @assign_div_expr -| 68 = @assign_rem_expr -| 69 = @assign_and_expr -| 70 = @assign_xor_expr -| 71 = @assign_or_expr -| 72 = @assign_lshift_expr -| 73 = @assign_rshift_expr -/* more */ -| 74 = @object_init_expr -| 75 = @collection_init_expr -| 76 = @array_init_expr -| 77 = @checked_expr -| 78 = @unchecked_expr -| 79 = @constructor_init_expr -| 80 = @add_event_expr -| 81 = @remove_event_expr -| 82 = @par_expr -| 83 = @local_var_decl_expr -| 84 = @lambda_expr -| 85 = @anonymous_method_expr -| 86 = @namespace_expr -/* dynamic */ -| 92 = @dynamic_element_access_expr -| 93 = @dynamic_member_access_expr -/* unsafe */ -| 100 = @pointer_indirection_expr -| 101 = @address_of_expr -| 102 = @sizeof_expr -/* async */ -| 103 = @await_expr -/* C# 6.0 */ -| 104 = @nameof_expr -| 105 = @interpolated_string_expr -| 106 = @unknown_expr -/* C# 7.0 */ -| 107 = @throw_expr -| 108 = @tuple_expr -| 109 = @local_function_invocation_expr -| 110 = @ref_expr -| 111 = @discard_expr -/* C# 8.0 */ -| 112 = @range_expr -| 113 = @index_expr -| 114 = @switch_expr -| 115 = @recursive_pattern_expr -| 116 = @property_pattern_expr -| 117 = @positional_pattern_expr -| 118 = @switch_case_expr -| 119 = @assign_coalesce_expr -| 120 = @suppress_nullable_warning_expr -| 121 = @namespace_access_expr -/* C# 9.0 */ -| 122 = @lt_pattern_expr -| 123 = @gt_pattern_expr -| 124 = @le_pattern_expr -| 125 = @ge_pattern_expr -; - -@switch = @switch_stmt | @switch_expr; -@case = @case_stmt | @switch_case_expr; -@pattern_match = @case | @is_expr; -@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; - -@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; -@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; -@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr - | @string_literal_expr | @null_literal_expr; - -@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; -@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; -@assign_event_expr = @add_event_expr | @remove_event_expr; - -@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr - | @assign_rem_expr -@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr - | @assign_lshift_expr | @assign_rshift_expr; - -@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr - | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; -@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; -@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; - -@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; -@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; -@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; - -@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr - | @event_access_expr | @dynamic_member_access_expr; - -@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; - -@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; - -@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; -@incr_op_expr = @pre_incr_expr | @post_incr_expr; -@decr_op_expr = @pre_decr_expr | @post_decr_expr; -@mut_op_expr = @incr_op_expr | @decr_op_expr; -@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; -@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; - -@ternary_log_op_expr = @conditional_expr; -@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; -@un_log_op_expr = @log_not_expr; -@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; - -@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr - | @rshift_expr; -@un_bit_op_expr = @bit_not_expr; -@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; - -@equality_op_expr = @eq_expr | @ne_expr; -@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; -@comp_expr = @equality_op_expr | @rel_op_expr; - -@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; - -@ternary_op = @ternary_log_op_expr; -@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; -@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr - | @pointer_indirection_expr | @address_of_expr; - -@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; - -@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr - | @delegate_invocation_expr | @object_creation_expr | @call_access_expr - | @local_function_invocation_expr; - -@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; - -@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr - | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; - -@throw_element = @throw_expr | @throw_stmt; - -implicitly_typed_array_creation( - unique int id: @array_creation_expr ref); - -explicitly_sized_array_creation( - unique int id: @array_creation_expr ref); - -stackalloc_array_creation( - unique int id: @array_creation_expr ref); - -mutator_invocation_mode( - unique int id: @operator_invocation_expr ref, - int mode: int ref /* prefix = 1, postfix = 2*/); - -expr_compiler_generated( - unique int id: @expr ref); - -expr_value( - unique int id: @expr ref, - string value: string ref); - -expr_call( - unique int caller_id: @expr ref, - int target_id: @callable ref); - -expr_access( - unique int accesser_id: @access_expr ref, - int target_id: @accessible ref); - -@accessible = @method | @assignable | @local_function | @namespace; - -expr_location( - unique int id: @expr ref, - int loc: @location ref); - -dynamic_member_name( - unique int id: @late_bindable_expr ref, - string name: string ref); - -@qualifiable_expr = @member_access_expr - | @method_invocation_expr - | @element_access_expr; - -conditional_access( - unique int id: @qualifiable_expr ref); - -expr_argument( - unique int id: @expr ref, - int mode: int ref); - /* mode is the same as params: value = 0, ref = 1, out = 2 */ - -expr_argument_name( - unique int id: @expr ref, - string name: string ref); - -/** CONTROL/DATA FLOW **/ - -@control_flow_element = @stmt | @expr; - -/* XML Files */ - -xmlEncoding ( - unique int id: @file ref, - string encoding: string ref); - -xmlDTDs( - unique int id: @xmldtd, - string root: string ref, - string publicId: string ref, - string systemId: string ref, - int fileid: @file ref); - -xmlElements( - unique int id: @xmlelement, - string name: string ref, - int parentid: @xmlparent ref, - int idx: int ref, - int fileid: @file ref); - -xmlAttrs( - unique int id: @xmlattribute, - int elementid: @xmlelement ref, - string name: string ref, - string value: string ref, - int idx: int ref, - int fileid: @file ref); - -xmlNs( - int id: @xmlnamespace, - string prefixName: string ref, - string URI: string ref, - int fileid: @file ref); - -xmlHasNs( - int elementId: @xmlnamespaceable ref, - int nsId: @xmlnamespace ref, - int fileid: @file ref); - -xmlComments( - unique int id: @xmlcomment, - string text: string ref, - int parentid: @xmlparent ref, - int fileid: @file ref); - -xmlChars( - unique int id: @xmlcharacters, - string text: string ref, - int parentid: @xmlparent ref, - int idx: int ref, - int isCDATA: int ref, - int fileid: @file ref); - -@xmlparent = @file | @xmlelement; -@xmlnamespaceable = @xmlelement | @xmlattribute; - -xmllocations( - int xmlElement: @xmllocatable ref, - int location: @location_default ref); - -@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; - -/* Comments */ - -commentline( - unique int id: @commentline, - int kind: int ref, - string text: string ref, - string rawtext: string ref); - -case @commentline.kind of - 0 = @singlelinecomment -| 1 = @xmldoccomment -| 2 = @multilinecomment; - -commentline_location( - unique int id: @commentline ref, - int loc: @location ref); - -commentblock( - unique int id : @commentblock); - -commentblock_location( - unique int id: @commentblock ref, - int loc: @location ref); - -commentblock_binding( - int id: @commentblock ref, - int entity: @element ref, - int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ - -commentblock_child( - int id: @commentblock ref, - int commentline: @commentline ref, - int index: int ref); - -/* ASP.NET */ - -case @asp_element.kind of - 0=@asp_close_tag -| 1=@asp_code -| 2=@asp_comment -| 3=@asp_data_binding -| 4=@asp_directive -| 5=@asp_open_tag -| 6=@asp_quoted_string -| 7=@asp_text -| 8=@asp_xml_directive; - -@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; - -asp_elements( - unique int id: @asp_element, - int kind: int ref, - int loc: @location ref); - -asp_comment_server(unique int comment: @asp_comment ref); -asp_code_inline(unique int code: @asp_code ref); -asp_directive_attribute( - int directive: @asp_directive ref, - int index: int ref, - string name: string ref, - int value: @asp_quoted_string ref); -asp_directive_name( - unique int directive: @asp_directive ref, - string name: string ref); -asp_element_body( - unique int element: @asp_element ref, - string body: string ref); -asp_tag_attribute( - int tag: @asp_open_tag ref, - int index: int ref, - string name: string ref, - int attribute: @asp_attribute ref); -asp_tag_name( - unique int tag: @asp_open_tag ref, - string name: string ref); -asp_tag_isempty(int tag: @asp_open_tag ref); - -/* Common Intermediate Language - CIL */ - -case @cil_instruction.opcode of - 0 = @cil_nop -| 1 = @cil_break -| 2 = @cil_ldarg_0 -| 3 = @cil_ldarg_1 -| 4 = @cil_ldarg_2 -| 5 = @cil_ldarg_3 -| 6 = @cil_ldloc_0 -| 7 = @cil_ldloc_1 -| 8 = @cil_ldloc_2 -| 9 = @cil_ldloc_3 -| 10 = @cil_stloc_0 -| 11 = @cil_stloc_1 -| 12 = @cil_stloc_2 -| 13 = @cil_stloc_3 -| 14 = @cil_ldarg_s -| 15 = @cil_ldarga_s -| 16 = @cil_starg_s -| 17 = @cil_ldloc_s -| 18 = @cil_ldloca_s -| 19 = @cil_stloc_s -| 20 = @cil_ldnull -| 21 = @cil_ldc_i4_m1 -| 22 = @cil_ldc_i4_0 -| 23 = @cil_ldc_i4_1 -| 24 = @cil_ldc_i4_2 -| 25 = @cil_ldc_i4_3 -| 26 = @cil_ldc_i4_4 -| 27 = @cil_ldc_i4_5 -| 28 = @cil_ldc_i4_6 -| 29 = @cil_ldc_i4_7 -| 30 = @cil_ldc_i4_8 -| 31 = @cil_ldc_i4_s -| 32 = @cil_ldc_i4 -| 33 = @cil_ldc_i8 -| 34 = @cil_ldc_r4 -| 35 = @cil_ldc_r8 -| 37 = @cil_dup -| 38 = @cil_pop -| 39 = @cil_jmp -| 40 = @cil_call -| 41 = @cil_calli -| 42 = @cil_ret -| 43 = @cil_br_s -| 44 = @cil_brfalse_s -| 45 = @cil_brtrue_s -| 46 = @cil_beq_s -| 47 = @cil_bge_s -| 48 = @cil_bgt_s -| 49 = @cil_ble_s -| 50 = @cil_blt_s -| 51 = @cil_bne_un_s -| 52 = @cil_bge_un_s -| 53 = @cil_bgt_un_s -| 54 = @cil_ble_un_s -| 55 = @cil_blt_un_s -| 56 = @cil_br -| 57 = @cil_brfalse -| 58 = @cil_brtrue -| 59 = @cil_beq -| 60 = @cil_bge -| 61 = @cil_bgt -| 62 = @cil_ble -| 63 = @cil_blt -| 64 = @cil_bne_un -| 65 = @cil_bge_un -| 66 = @cil_bgt_un -| 67 = @cil_ble_un -| 68 = @cil_blt_un -| 69 = @cil_switch -| 70 = @cil_ldind_i1 -| 71 = @cil_ldind_u1 -| 72 = @cil_ldind_i2 -| 73 = @cil_ldind_u2 -| 74 = @cil_ldind_i4 -| 75 = @cil_ldind_u4 -| 76 = @cil_ldind_i8 -| 77 = @cil_ldind_i -| 78 = @cil_ldind_r4 -| 79 = @cil_ldind_r8 -| 80 = @cil_ldind_ref -| 81 = @cil_stind_ref -| 82 = @cil_stind_i1 -| 83 = @cil_stind_i2 -| 84 = @cil_stind_i4 -| 85 = @cil_stind_i8 -| 86 = @cil_stind_r4 -| 87 = @cil_stind_r8 -| 88 = @cil_add -| 89 = @cil_sub -| 90 = @cil_mul -| 91 = @cil_div -| 92 = @cil_div_un -| 93 = @cil_rem -| 94 = @cil_rem_un -| 95 = @cil_and -| 96 = @cil_or -| 97 = @cil_xor -| 98 = @cil_shl -| 99 = @cil_shr -| 100 = @cil_shr_un -| 101 = @cil_neg -| 102 = @cil_not -| 103 = @cil_conv_i1 -| 104 = @cil_conv_i2 -| 105 = @cil_conv_i4 -| 106 = @cil_conv_i8 -| 107 = @cil_conv_r4 -| 108 = @cil_conv_r8 -| 109 = @cil_conv_u4 -| 110 = @cil_conv_u8 -| 111 = @cil_callvirt -| 112 = @cil_cpobj -| 113 = @cil_ldobj -| 114 = @cil_ldstr -| 115 = @cil_newobj -| 116 = @cil_castclass -| 117 = @cil_isinst -| 118 = @cil_conv_r_un -| 121 = @cil_unbox -| 122 = @cil_throw -| 123 = @cil_ldfld -| 124 = @cil_ldflda -| 125 = @cil_stfld -| 126 = @cil_ldsfld -| 127 = @cil_ldsflda -| 128 = @cil_stsfld -| 129 = @cil_stobj -| 130 = @cil_conv_ovf_i1_un -| 131 = @cil_conv_ovf_i2_un -| 132 = @cil_conv_ovf_i4_un -| 133 = @cil_conv_ovf_i8_un -| 134 = @cil_conv_ovf_u1_un -| 135 = @cil_conv_ovf_u2_un -| 136 = @cil_conv_ovf_u4_un -| 137 = @cil_conv_ovf_u8_un -| 138 = @cil_conv_ovf_i_un -| 139 = @cil_conv_ovf_u_un -| 140 = @cil_box -| 141 = @cil_newarr -| 142 = @cil_ldlen -| 143 = @cil_ldelema -| 144 = @cil_ldelem_i1 -| 145 = @cil_ldelem_u1 -| 146 = @cil_ldelem_i2 -| 147 = @cil_ldelem_u2 -| 148 = @cil_ldelem_i4 -| 149 = @cil_ldelem_u4 -| 150 = @cil_ldelem_i8 -| 151 = @cil_ldelem_i -| 152 = @cil_ldelem_r4 -| 153 = @cil_ldelem_r8 -| 154 = @cil_ldelem_ref -| 155 = @cil_stelem_i -| 156 = @cil_stelem_i1 -| 157 = @cil_stelem_i2 -| 158 = @cil_stelem_i4 -| 159 = @cil_stelem_i8 -| 160 = @cil_stelem_r4 -| 161 = @cil_stelem_r8 -| 162 = @cil_stelem_ref -| 163 = @cil_ldelem -| 164 = @cil_stelem -| 165 = @cil_unbox_any -| 179 = @cil_conv_ovf_i1 -| 180 = @cil_conv_ovf_u1 -| 181 = @cil_conv_ovf_i2 -| 182 = @cil_conv_ovf_u2 -| 183 = @cil_conv_ovf_i4 -| 184 = @cil_conv_ovf_u4 -| 185 = @cil_conv_ovf_i8 -| 186 = @cil_conv_ovf_u8 -| 194 = @cil_refanyval -| 195 = @cil_ckinfinite -| 198 = @cil_mkrefany -| 208 = @cil_ldtoken -| 209 = @cil_conv_u2 -| 210 = @cil_conv_u1 -| 211 = @cil_conv_i -| 212 = @cil_conv_ovf_i -| 213 = @cil_conv_ovf_u -| 214 = @cil_add_ovf -| 215 = @cil_add_ovf_un -| 216 = @cil_mul_ovf -| 217 = @cil_mul_ovf_un -| 218 = @cil_sub_ovf -| 219 = @cil_sub_ovf_un -| 220 = @cil_endfinally -| 221 = @cil_leave -| 222 = @cil_leave_s -| 223 = @cil_stind_i -| 224 = @cil_conv_u -| 65024 = @cil_arglist -| 65025 = @cil_ceq -| 65026 = @cil_cgt -| 65027 = @cil_cgt_un -| 65028 = @cil_clt -| 65029 = @cil_clt_un -| 65030 = @cil_ldftn -| 65031 = @cil_ldvirtftn -| 65033 = @cil_ldarg -| 65034 = @cil_ldarga -| 65035 = @cil_starg -| 65036 = @cil_ldloc -| 65037 = @cil_ldloca -| 65038 = @cil_stloc -| 65039 = @cil_localloc -| 65041 = @cil_endfilter -| 65042 = @cil_unaligned -| 65043 = @cil_volatile -| 65044 = @cil_tail -| 65045 = @cil_initobj -| 65046 = @cil_constrained -| 65047 = @cil_cpblk -| 65048 = @cil_initblk -| 65050 = @cil_rethrow -| 65052 = @cil_sizeof -| 65053 = @cil_refanytype -| 65054 = @cil_readonly -; - -// CIL ignored instructions - -@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; - -// CIL local/parameter/field access - -@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; -@cil_starg_any = @cil_starg | @cil_starg_s; - -@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; -@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; - -@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; -@cil_stfld_any = @cil_stfld | @cil_stsfld; - -@cil_local_access = @cil_stloc_any | @cil_ldloc_any; -@cil_arg_access = @cil_starg_any | @cil_ldarg_any; -@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; -@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; - -@cil_stack_access = @cil_local_access | @cil_arg_access; -@cil_field_access = @cil_ldfld_any | @cil_stfld_any; - -@cil_access = @cil_read_access | @cil_write_access; - -// CIL constant/literal instructions - -@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; - -@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | - @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; - -@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; - -@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; - -// Control flow - -@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; -@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | - @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | - @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | - @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; -@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; -@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; -@cil_leave_any = @cil_leave | @cil_leave_s; -@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; - -// CIL call instructions - -@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; - -// CIL expression instructions - -@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | - @cil_newarr | @cil_ldtoken | @cil_sizeof | - @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; - -@cil_unary_expr = - @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| - @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | - @cil_ldind | @cil_unbox; - -@cil_conversion_operation = - @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | - @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | - @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | - @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | - @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | - @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | - @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | - @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | - @cil_conv_i | @cil_conv_u | @cil_conv_r_un; - -@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | - @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; - -@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | - @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; - -@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; - -@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; - -@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | - @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | - @cil_sub_ovf | @cil_sub_ovf_un; - -@cil_unary_bitwise_operation = @cil_not; - -@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; - -@cil_unary_arithmetic_operation = @cil_neg; - -@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; - -// Elements that retrieve an address of something -@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; - -// CIL array instructions - -@cil_read_array = - @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | - @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | - @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; - -@cil_write_array = @cil_stelem | @cil_stelem_ref | - @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | - @cil_stelem_r4 | @cil_stelem_r8; - -@cil_throw_any = @cil_throw | @cil_rethrow; - -#keyset[impl, index] -cil_instruction( - unique int id: @cil_instruction, - int opcode: int ref, - int index: int ref, - int impl: @cil_method_implementation ref); - -cil_jump( - unique int instruction: @cil_jump ref, - int target: @cil_instruction ref); - -cil_access( - unique int instruction: @cil_instruction ref, - int target: @cil_accessible ref); - -cil_value( - unique int instruction: @cil_literal ref, - string value: string ref); - -#keyset[instruction, index] -cil_switch( - int instruction: @cil_switch ref, - int index: int ref, - int target: @cil_instruction ref); - -cil_instruction_location( - unique int id: @cil_instruction ref, - int loc: @location ref); - -cil_type_location( - int id: @cil_type ref, - int loc: @location ref); - -cil_method_location( - int id: @cil_method ref, - int loc: @location ref); - -@cil_namespace = @namespace; - -@cil_type_container = @cil_type | @cil_namespace | @cil_method; - -case @cil_type.kind of - 0 = @cil_valueorreftype -| 1 = @cil_typeparameter -| 2 = @cil_array_type -| 3 = @cil_pointer_type -; - -cil_type( - unique int id: @cil_type, - string name: string ref, - int kind: int ref, - int parent: @cil_type_container ref, - int sourceDecl: @cil_type ref); - -cil_pointer_type( - unique int id: @cil_pointer_type ref, - int pointee: @cil_type ref); - -cil_array_type( - unique int id: @cil_array_type ref, - int element_type: @cil_type ref, - int rank: int ref); - -cil_method( - unique int id: @cil_method, - string name: string ref, - int parent: @cil_type ref, - int return_type: @cil_type ref); - -cil_method_source_declaration( - unique int method: @cil_method ref, - int source: @cil_method ref); - -cil_method_implementation( - unique int id: @cil_method_implementation, - int method: @cil_method ref, - int location: @assembly ref); - -cil_implements( - int id: @cil_method ref, - int decl: @cil_method ref); - -#keyset[parent, name] -cil_field( - unique int id: @cil_field, - int parent: @cil_type ref, - string name: string ref, - int field_type: @cil_type ref); - -@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; -@cil_named_element = @cil_declaration | @cil_namespace; -@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; -@cil_accessible = @cil_declaration; -@cil_variable = @cil_field | @cil_stack_variable; -@cil_stack_variable = @cil_local_variable | @cil_parameter; -@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; - -#keyset[method, index] -cil_parameter( - unique int id: @cil_parameter, - int method: @cil_method ref, - int index: int ref, - int param_type: @cil_type ref); - -cil_parameter_in(unique int id: @cil_parameter ref); -cil_parameter_out(unique int id: @cil_parameter ref); - -cil_setter(unique int prop: @cil_property ref, - int method: @cil_method ref); - -cil_getter(unique int prop: @cil_property ref, - int method: @cil_method ref); - -cil_adder(unique int event: @cil_event ref, - int method: @cil_method ref); - -cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); - -cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); - -cil_property( - unique int id: @cil_property, - int parent: @cil_type ref, - string name: string ref, - int property_type: @cil_type ref); - -#keyset[parent, name] -cil_event(unique int id: @cil_event, - int parent: @cil_type ref, - string name: string ref, - int event_type: @cil_type ref); - -#keyset[impl, index] -cil_local_variable( - unique int id: @cil_local_variable, - int impl: @cil_method_implementation ref, - int index: int ref, - int var_type: @cil_type ref); - -// CIL handlers (exception handlers etc). - -case @cil_handler.kind of - 0 = @cil_catch_handler -| 1 = @cil_filter_handler -| 2 = @cil_finally_handler -| 4 = @cil_fault_handler -; - -#keyset[impl, index] -cil_handler( - unique int id: @cil_handler, - int impl: @cil_method_implementation ref, - int index: int ref, - int kind: int ref, - int try_start: @cil_instruction ref, - int try_end: @cil_instruction ref, - int handler_start: @cil_instruction ref); - -cil_handler_filter( - unique int id: @cil_handler ref, - int filter_start: @cil_instruction ref); - -cil_handler_type( - unique int id: @cil_handler ref, - int catch_type: @cil_type ref); - -@cil_controlflow_node = @cil_entry_point | @cil_instruction; - -@cil_entry_point = @cil_method_implementation | @cil_handler; - -@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; - -cil_method_stack_size( - unique int method: @cil_method_implementation ref, - int size: int ref); - -// CIL modifiers - -cil_public(int id: @cil_member ref); -cil_private(int id: @cil_member ref); -cil_protected(int id: @cil_member ref); -cil_internal(int id: @cil_member ref); -cil_static(int id: @cil_member ref); -cil_sealed(int id: @cil_member ref); -cil_virtual(int id: @cil_method ref); -cil_abstract(int id: @cil_member ref); -cil_class(int id: @cil_type ref); -cil_interface(int id: @cil_type ref); -cil_security(int id: @cil_member ref); -cil_requiresecobject(int id: @cil_method ref); -cil_specialname(int id: @cil_method ref); -cil_newslot(int id: @cil_method ref); - -cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); -cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); - -#keyset[unbound, index] -cil_type_parameter( - int unbound: @cil_member ref, - int index: int ref, - int param: @cil_typeparameter ref); - -#keyset[bound, index] -cil_type_argument( - int bound: @cil_member ref, - int index: int ref, - int t: @cil_type ref); - -// CIL type parameter constraints - -cil_typeparam_covariant(int tp: @cil_typeparameter ref); -cil_typeparam_contravariant(int tp: @cil_typeparameter ref); -cil_typeparam_class(int tp: @cil_typeparameter ref); -cil_typeparam_struct(int tp: @cil_typeparameter ref); -cil_typeparam_new(int tp: @cil_typeparameter ref); -cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); - -// CIL attributes - -cil_attribute( - unique int attributeid: @cil_attribute, - int element: @cil_declaration ref, - int constructor: @cil_method ref); - -#keyset[attribute_id, param] -cil_attribute_named_argument( - int attribute_id: @cil_attribute ref, - string param: string ref, - string value: string ref); - -#keyset[attribute_id, index] -cil_attribute_positional_argument( - int attribute_id: @cil_attribute ref, - int index: int ref, - string value: string ref); - - -// Common .Net data model covering both C# and CIL - -// Common elements -@dotnet_element = @element | @cil_element; -@dotnet_named_element = @named_element | @cil_named_element; -@dotnet_callable = @callable | @cil_method; -@dotnet_variable = @variable | @cil_variable; -@dotnet_field = @field | @cil_field; -@dotnet_parameter = @parameter | @cil_parameter; -@dotnet_declaration = @declaration | @cil_declaration; -@dotnet_member = @member | @cil_member; -@dotnet_event = @event | @cil_event; -@dotnet_property = @property | @cil_property | @indexer; - -// Common types -@dotnet_type = @type | @cil_type; -@dotnet_call = @call | @cil_call_any; -@dotnet_throw = @throw_element | @cil_throw_any; -@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; -@dotnet_typeparameter = @type_parameter | @cil_typeparameter; -@dotnet_array_type = @array_type | @cil_array_type; -@dotnet_pointer_type = @pointer_type | @cil_pointer_type; -@dotnet_type_parameter = @type_parameter | @cil_typeparameter; -@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; - -// Attributes -@dotnet_attribute = @attribute | @cil_attribute; - -// Expressions -@dotnet_expr = @expr | @cil_expr; - -// Literals -@dotnet_literal = @literal_expr | @cil_literal; -@dotnet_string_literal = @string_literal_expr | @cil_ldstr; -@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; -@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; -@dotnet_null_literal = @null_literal_expr | @cil_ldnull; - -@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | - @callable | @value_or_ref_type | @void_type; - -#keyset[entity, location] -metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties b/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties deleted file mode 100644 index ff3b5413d2c..00000000000 --- a/csharp/upgrades/eedef9359e1e4e1ebcf2a1dc9d2ae75d4ea4d160/upgrade.properties +++ /dev/null @@ -1,2 +0,0 @@ -description: Add 'relational_pattern_expr' and its four subtypes ('<', '>', '<=', '>=') -compatibility: backwards From 452417509f4220462a4cf0b9b536f1dfd3ecfd8b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 27 Nov 2020 17:34:43 +0100 Subject: [PATCH 96/97] Design Patterns: Reword advice on imports of subclasses I had totally overlooked the fact that this doesn't only apply to abstract classes. --- docs/ql-design-patterns.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index ad3137932d8..53327049516 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -74,8 +74,10 @@ You can, of course, do the same without the `::Range` pattern, but it's a little If you only had an `abstract class EscapeFunction { ... }`, then `JsEscapeFunction` would need to be implemented in a slightly tricky way to prevent it from extending `EscapeFunction` (instead of refining it). You would have to give it a charpred `this instanceof EscapeFunction`, which looks useless but isn't. And additionally, you'd have to provide trivial `none()` overrides of all the abstract predicates defined in `EscapeFunction`. This is all pretty awkward, and we can avoid it by distinguishing between `EscapeFunction` and `EscapeFunction::Range`. -## Importing all subclasses of abstract base class +## Importing all subclasses of a class -When providing an abstract class, you should ensure that all subclasses are included when the abstract class is (unless you have good reason not to). Otherwise you risk having different meanings of the abstract class depending on what you happen to import. +Importing new files can modify the behaviour of the standard library, by introducing new subtypes of `abstract` classes, by introducing new multiple inheritance relationships, or by overriding predicates. This can change query results and force evaluator cache misses. -One example where this _does not_ apply: `DataFlow::Configuration` and its variants are abstract, but we generally do not want to import all configurations into the same scope at once. +Therefore, unless you have good reason not to, you should ensure that all subclasses are included when the base-class is (to the extent possible). + +One example where this _does not_ apply: `DataFlow::Configuration` and its variants are meant to be subclassed, but we generally do not want to import all configurations into the same scope at once. From faa5c220c59faf938d515da91814d852fe160ed9 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 27 Nov 2020 17:36:54 +0100 Subject: [PATCH 97/97] Design Patterns: Add advice on abstract classes --- docs/ql-design-patterns.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/ql-design-patterns.md b/docs/ql-design-patterns.md index 53327049516..6984088e925 100644 --- a/docs/ql-design-patterns.md +++ b/docs/ql-design-patterns.md @@ -81,3 +81,32 @@ Importing new files can modify the behaviour of the standard library, by introdu Therefore, unless you have good reason not to, you should ensure that all subclasses are included when the base-class is (to the extent possible). One example where this _does not_ apply: `DataFlow::Configuration` and its variants are meant to be subclassed, but we generally do not want to import all configurations into the same scope at once. + + +## Abstract classes as open or closed unions + +A class declared as `abstract` in QL represents a union of its direct subtypes (restricted by the intersections of its supertypes and subject to its characteristic predicate). Depending on context, we may want this union to be considered "open" or "closed". + +An open union is generally used for extensibility. For example, the abstract classes suggested by the `::Range` design pattern are explicitly intended as extension hooks. As another example, the `DataFlow::Configuration` design pattern provides an abstract class that is intended to be subclassed as a configuration mechanism. + +A closed union is a class for which we do not expect users of the library to add more values. Historically, we have occasionally modelled this as `abstract` classes in QL, but these days that would be considered an anti-pattern: Abstract classes that are intended to be closed behave in surprising ways when subclassed by library users, and importing libraries that include derived classes can invalidate compilation caches and subvert the meaning of the program. + +As an example, suppose we want to define a `BinaryExpr` class, which has subtypes of `PlusExpr`, `MinusExpr`, and so on. Morally, this represents a closed union: We do not anticipate new kinds of `BinaryExpr` being added. Therefore, it would be undesirable to model it as an abstract class: + +```ql +/** ANTI-PATTERN */ +abstract class BinaryExpr extends Expr { + Expr getLhs() { result = this.getChild(0) } + Expr getRight() { result = this.getChild(1) } +} + +class PlusExpr extends BinaryExpr {} +class MinusExpr extends BinaryExpr {} +... +``` + +Instead, the `BinaryExpr` class should be non-`abstract`, and we have the following options for specifying its extent: + +- Define a dbscheme type `@binary_expr = @plus_expr | @minus_expr | ...` and add it as an additional super-class for `BinaryExpr`. +- Define a type alias `class RawBinaryExpr = @plus_expr | @minus_expr | ...` and add it as an additional super-class for `BinaryExpr`. +- Add a characteristic predicate of `BinaryExpr() { this instanceof PlusExpr or this instanceof MinusExpr or ... }`.