From e6242fd34912535e9325a6c423e2d7f26d672a22 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:43:30 +0100 Subject: [PATCH 01/43] Add ql/use-set-literal query. --- ql/src/queries/style/UseSetLiteral.ql | 89 +++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 ql/src/queries/style/UseSetLiteral.ql diff --git a/ql/src/queries/style/UseSetLiteral.ql b/ql/src/queries/style/UseSetLiteral.ql new file mode 100644 index 00000000000..0657cef831d --- /dev/null +++ b/ql/src/queries/style/UseSetLiteral.ql @@ -0,0 +1,89 @@ +/** + * @name Use a set literal in place of `or` + * @description A chain of `or`s can be replaced with a set literal, improving readability. + * @kind problem + * @problem.severity recommendation + * @id ql/use-set-literal + * @tags maintainability + * @precision high + */ + +import ql +import codeql_ql.ast.internal.Predicate // TODO: for PredicateOrBuiltin + +/** + * A chain of disjunctions treated as one object. For example the following is + * a chain of disjunctions with three operands: + * ``` + * a or b or c + * ``` + */ +class DisjunctionChain extends Disjunction { + DisjunctionChain() { not exists(Disjunction parent | parent.getAnOperand() = this) } + + /** + * Gets any operand of the chain. + */ + Formula getAnOperandRec() { + result = getAnOperand*() and + not result instanceof Disjunction + } +} + +/** + * An equality comparison with a `Literal`. For example: + * ``` + * x = 4 + * ``` + */ +class EqualsLiteral extends ComparisonFormula { + EqualsLiteral() { + getSymbol() = "=" and + getAnOperand() instanceof Literal + } +} + +/** + * A chain of disjunctions where each operand is an equality comparison between + * the same thing and various `Literal`s. For example: + * ``` + * x = 4 or + * x = 5 or + * x = 6 + * ``` + */ +class DisjunctionEqualsLiteral extends DisjunctionChain { + DisjunctionEqualsLiteral() { + // VarAccess on the same variable + exists(VarDef v | + forex(Formula f | f = getAnOperandRec() | + f.(EqualsLiteral).getAnOperand().(VarAccess).getDeclaration() = v + ) + ) + or + // FieldAccess on the same variable + exists(VarDecl v | + forex(Formula f | f = getAnOperandRec() | + f.(EqualsLiteral).getAnOperand().(FieldAccess).getDeclaration() = v + ) + ) + or + // ThisAccess + forex(Formula f | f = getAnOperandRec() | + f.(EqualsLiteral).getAnOperand() instanceof ThisAccess + ) + or + // ResultAccess + forex(Formula f | f = getAnOperandRec() | + f.(EqualsLiteral).getAnOperand() instanceof ResultAccess + ) + // (in principle something like GlobalValueNumbering could be used to generalize this) + } +} + +from DisjunctionChain d, int c +where + d instanceof DisjunctionEqualsLiteral and + c = count(d.getAnOperandRec()) and + c >= 4 +select d, "This formula can be replaced with equality on a set literal, improving readability." From c8c23a6eb495e16712f30ca9ed36e53a4da08506 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:58:32 +0100 Subject: [PATCH 02/43] Support hasName(x) pattern as well. --- ql/src/queries/style/UseSetLiteral.ql | 47 +++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/ql/src/queries/style/UseSetLiteral.ql b/ql/src/queries/style/UseSetLiteral.ql index 0657cef831d..4bd13a22c38 100644 --- a/ql/src/queries/style/UseSetLiteral.ql +++ b/ql/src/queries/style/UseSetLiteral.ql @@ -81,9 +81,50 @@ class DisjunctionEqualsLiteral extends DisjunctionChain { } } -from DisjunctionChain d, int c +/** + * A call with a single `Literal` argument. For example: + * ``` + * myPredicate(4) + * ``` + */ +class CallLiteral extends Call { + CallLiteral() { + getNumberOfArguments() = 1 and + getArgument(0) instanceof Literal + } +} + +/** + * A chain of disjunctions where each operand is a call to the same predicate + * using various `Literal`s. For example: + * ``` + * myPredicate(4) or + * myPredicate(5) or + * myPredicate(6) + * ``` + */ +class DisjunctionPredicateLiteral extends DisjunctionChain { + DisjunctionPredicateLiteral() { + // Call to the same target + exists(PredicateOrBuiltin target | + forex(Formula f | f = getAnOperandRec() | f.(CallLiteral).getTarget() = target) + ) + } +} + +from DisjunctionChain d, string msg, int c where - d instanceof DisjunctionEqualsLiteral and + ( + d instanceof DisjunctionEqualsLiteral and + msg = + "This formula of " + c.toString() + + " comparisons can be replaced with a single equality on a set literal, improving readability." + or + d instanceof DisjunctionPredicateLiteral and + msg = + "This formula of " + c.toString() + + " predicate calls can be replaced with a single call on a set literal, improving readability." + ) and c = count(d.getAnOperandRec()) and c >= 4 -select d, "This formula can be replaced with equality on a set literal, improving readability." +select d, msg From 0704ab7bd325722e50baf3b70fd3150b212a2c2d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 13 Oct 2021 14:25:14 +0100 Subject: [PATCH 03/43] Add tests. --- .../UseSetLiteral/UseSetLiteral.expected | 8 ++ .../style/UseSetLiteral/UseSetLiteral.qlref | 1 + ql/test/queries/style/UseSetLiteral/test.qll | 127 ++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected create mode 100644 ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref create mode 100644 ql/test/queries/style/UseSetLiteral/test.qll diff --git a/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected new file mode 100644 index 00000000000..053a8b55512 --- /dev/null +++ b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected @@ -0,0 +1,8 @@ +| test.qll:4:2:7:6 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:29:2:32:9 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | +| test.qll:43:2:46:11 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:62:4:65:11 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:67:4:70:10 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:72:4:75:10 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:89:2:92:8 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | +| test.qll:126:2:126:37 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | diff --git a/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref new file mode 100644 index 00000000000..d4047ebc29f --- /dev/null +++ b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref @@ -0,0 +1 @@ +queries/style/UseSetLiteral.ql \ No newline at end of file diff --git a/ql/test/queries/style/UseSetLiteral/test.qll b/ql/test/queries/style/UseSetLiteral/test.qll new file mode 100644 index 00000000000..5016e032c11 --- /dev/null +++ b/ql/test/queries/style/UseSetLiteral/test.qll @@ -0,0 +1,127 @@ +import ql + +predicate test1(int a) { + a = 1 or // BAD + a = 2 or + a = 3 or + a = 4 +} + +predicate test2(int a) { + a = [1, 2, 3, 4] // GOOD +} + +predicate test3(int a) { + a = 1 and // GOOD (for the purposes of this query) + a = 2 and + a = 3 and + a = 4 +} + +bindingset[a] predicate test4(int a) { + a < 1 or // GOOD (for the purposes of this query) + a = 2 or + a >= 3 or + a > 4 +} + +predicate test5() { + test1(1) or // BAD + test1(2) or + test1(3) or + test1(4) +} + +predicate test6() { + test1(1) or // GOOD + test2(2) or + test3(3) or + test4(4) +} + +int test7() { + 1 = result or // BAD + 2 = result or + 3 = result or + 4 = result +} + +predicate test8() { + test7() = 1 or // BAD [NOT DETECTED] + test7() = 2 or + test7() = 3 or + test7() = 4 +} + +class MyTest8Class extends int +{ + string s; + + MyTest8Class() { + ( + this = 1 or // BAD + this = 2 or + this = 3 or + this = 4 + ) and ( + s = "1" or // BAD + s = "2" or + s = "3" or + s = "4" + ) and exists(float f | + f = 1.0 or // BAD + f = 1.5 or + f = 2.0 or + f = 2.5 + ) + } + + predicate is(int x) { + x = this + } + + int get() { + result = this + } +} + +predicate test9(MyTest8Class c) { + c.is(1) or // BAD + c.is(2) or + c.is(3) or + c.is(4) +} + +predicate test10(MyTest8Class c) { + c.get() = 1 or // BAD [NOT DETECTED] + c.get() = 2 or + c.get() = 3 or + c.get() = 4 +} + +bindingset[a, b, c, d] predicate test11(int a, int b, int c, int d) { + a = 1 or // GOOD + b = 2 or + c = 3 or + d = 4 +} + +bindingset[a, b] predicate test12(int a, int b) { + a = 1 or // BAD [NOT DETECTED] + a = 2 or + a = 3 or + a = 4 or + b = 0 +} + +predicate test13(int a, int b) { + (a = 1 and b = 1) or // GOOD + (a = 2 and b = 4) or + (a = 3 and b = 9) or + (a = 4 and b = 16) +} + +from int a +where + a = 1 or ((a = 2 or a = 3) or a = 4) // BAD +select a From 5a519c5089c3da66289cdeb249ffb8c238c765f6 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 14 Oct 2021 08:50:51 +0000 Subject: [PATCH 04/43] Add test for `override` This test demonstrates that our handling of `override` is incorrect. Quick-eval'ing the `test` predicate produces the following output: | f | i | j | +---+-----+-----+ | 1 | 10 | 10 | | 1 | 10 | 100 | | 1 | 100 | 10 | | 1 | 100 | 100 | | 2 | 20 | 20 | | 3 | 3 | 3 | this demonstrates that `f.bar` and `f.baz` can resolve to all predicates of that name in the file. However, at present we only capture the calls to members on `Foo`. --- ql/test/callgraph/Overrides.qll | 30 ++++++++++++++++++++++++++++ ql/test/callgraph/callgraph.expected | 7 +++++++ 2 files changed, 37 insertions(+) create mode 100644 ql/test/callgraph/Overrides.qll diff --git a/ql/test/callgraph/Overrides.qll b/ql/test/callgraph/Overrides.qll new file mode 100644 index 00000000000..1adcab1b949 --- /dev/null +++ b/ql/test/callgraph/Overrides.qll @@ -0,0 +1,30 @@ +import ql + +class Foo extends int { + Foo() { this in [1, 2, 3] } + + int bar() { result = this } + + predicate baz(int i) { i = this.bar() } +} + +class Bar extends Foo { + Bar() { this = [1, 2] } + + override int bar() { result = 10 * this } + + override predicate baz(int i) { i = this.bar() } +} + +class Baz extends Foo { + Baz() { this = 1 } + + override int bar() { result = 100 * this } + + override predicate baz(int i) { i = this.bar() } +} + +query predicate test(Foo f, int i, int j) { + f.bar() = i and + f.baz(j) +} diff --git a/ql/test/callgraph/callgraph.expected b/ql/test/callgraph/callgraph.expected index 39ba4dd101a..6638c45993c 100644 --- a/ql/test/callgraph/callgraph.expected +++ b/ql/test/callgraph/callgraph.expected @@ -6,3 +6,10 @@ | Foo.qll:29:5:29:16 | PredicateCall | Foo.qll:26:3:26:32 | ClasslessPredicate alias2 | | Foo.qll:31:5:31:12 | PredicateCall | Foo.qll:22:3:22:32 | ClasslessPredicate myThing0 | | Foo.qll:31:5:31:12 | PredicateCall | Foo.qll:24:3:24:32 | ClasslessPredicate alias0 | +| Overrides.qll:8:30:8:39 | MemberCall | Overrides.qll:6:3:6:29 | ClassPredicate bar | +| Overrides.qll:16:39:16:48 | MemberCall | Overrides.qll:6:3:6:29 | ClassPredicate bar | +| Overrides.qll:16:39:16:48 | MemberCall | Overrides.qll:14:12:14:43 | ClassPredicate bar | +| Overrides.qll:24:39:24:48 | MemberCall | Overrides.qll:6:3:6:29 | ClassPredicate bar | +| Overrides.qll:24:39:24:48 | MemberCall | Overrides.qll:22:12:22:44 | ClassPredicate bar | +| Overrides.qll:28:3:28:9 | MemberCall | Overrides.qll:6:3:6:29 | ClassPredicate bar | +| Overrides.qll:29:3:29:10 | MemberCall | Overrides.qll:8:3:8:41 | ClassPredicate baz | From 323ccc8cea0f996f0d95f4455f385e27303af404 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 13 Oct 2021 14:31:23 +0100 Subject: [PATCH 05/43] Add query to find non US spelling --- ql/src/queries/style/docs/NonUSSpelling.ql | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ql/src/queries/style/docs/NonUSSpelling.ql diff --git a/ql/src/queries/style/docs/NonUSSpelling.ql b/ql/src/queries/style/docs/NonUSSpelling.ql new file mode 100644 index 00000000000..8861629ca87 --- /dev/null +++ b/ql/src/queries/style/docs/NonUSSpelling.ql @@ -0,0 +1,38 @@ +/** + * @name Non US spelling + * @description QLDocs shold use US spelling. + * @kind problem + * @problem.severity warning + * @id ql/non-us-spelling + * @tags maintainability + * @precision very-high + */ + +import ql + +predicate non_us_word(string wrong, string right) { + exists(string s | + wrong = s.splitAt("/", 0) and + right = s.splitAt("/", 1) and + s = ["colour/color", "authorise/authorize", "analyse/analyze"] + ) +} + +bindingset[s] +predicate contains_non_us_spelling(string s, string wrong, string right) { + non_us_word(wrong, right) and + ( + s.matches("%" + wrong + "%") and + wrong != "analyse" + or + // analyses (as a noun) is fine + s.regexpMatch(".*analyse[^s].*") and + wrong = "analyse" + ) +} + +from QLDoc doc, string wrong, string right +where contains_non_us_spelling(doc.getContents().toLowerCase(), wrong, right) +select doc, + "This QLDoc comment contains the non-US spelling '" + wrong + "', which should instead be '" + + right + "'." From fd3c53da9b7f9409d3380f4596fa7085b3e6407d Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 13 Oct 2021 14:47:41 +0100 Subject: [PATCH 06/43] Add query for class docs that don't start with an article. Returns quite a few results, many of which seem to be TPs. --- ql/src/queries/style/docs/ClassDocs.ql | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ql/src/queries/style/docs/ClassDocs.ql diff --git a/ql/src/queries/style/docs/ClassDocs.ql b/ql/src/queries/style/docs/ClassDocs.ql new file mode 100644 index 00000000000..5e335da7109 --- /dev/null +++ b/ql/src/queries/style/docs/ClassDocs.ql @@ -0,0 +1,26 @@ +/** + * @name Class QLDoc style. + * @description The QLDoc for a class should start with "A", "An", or "The". + * @kind problem + * @problem.severity warning + * @id ql/class-doc-style + * @tags maintainability + * @precision very-high + */ + +import ql + +bindingset[s] +predicate badStyle(string s) { + not s.replaceAll("/**", "") + .replaceAll("*", "") + .splitAt("\n") + .trim() + .matches(["A %", "An %", "The %"]) +} + +from Class c +where + badStyle(c.getQLDoc().getContents()) and + not c.isPrivate() +select c.getQLDoc(), "The QLDoc for a class should start with 'A', 'An', or 'The'." From 0f71066aaa24ef6922c431259c559e060028398a Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 14 Oct 2021 11:15:41 +0100 Subject: [PATCH 07/43] Allow comments preceded by INTERNAL --- ql/src/queries/style/docs/ClassDocs.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/queries/style/docs/ClassDocs.ql b/ql/src/queries/style/docs/ClassDocs.ql index 5e335da7109..6bd67383712 100644 --- a/ql/src/queries/style/docs/ClassDocs.ql +++ b/ql/src/queries/style/docs/ClassDocs.ql @@ -16,7 +16,7 @@ predicate badStyle(string s) { .replaceAll("*", "") .splitAt("\n") .trim() - .matches(["A %", "An %", "The %"]) + .matches(["A %", "An %", "The %", "INTERNAL"]) } from Class c From b4a05804fad25da81838e147b28913ed3a1a20c2 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 14 Oct 2021 11:35:25 +0100 Subject: [PATCH 08/43] Also allow deprecated headers --- ql/src/queries/style/docs/ClassDocs.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/queries/style/docs/ClassDocs.ql b/ql/src/queries/style/docs/ClassDocs.ql index 6bd67383712..1dad0867996 100644 --- a/ql/src/queries/style/docs/ClassDocs.ql +++ b/ql/src/queries/style/docs/ClassDocs.ql @@ -16,7 +16,7 @@ predicate badStyle(string s) { .replaceAll("*", "") .splitAt("\n") .trim() - .matches(["A %", "An %", "The %", "INTERNAL"]) + .matches(["A %", "An %", "The %", "INTERNAL%", "DEPRECATED%"]) } from Class c From 9b52ad2d3d00ef349493ba43c9c22af94dfa647d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 13 Oct 2021 15:17:00 +0100 Subject: [PATCH 09/43] Work around import of internal file. --- ql/src/queries/style/UseSetLiteral.ql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ql/src/queries/style/UseSetLiteral.ql b/ql/src/queries/style/UseSetLiteral.ql index 4bd13a22c38..0481ac39299 100644 --- a/ql/src/queries/style/UseSetLiteral.ql +++ b/ql/src/queries/style/UseSetLiteral.ql @@ -9,7 +9,6 @@ */ import ql -import codeql_ql.ast.internal.Predicate // TODO: for PredicateOrBuiltin /** * A chain of disjunctions treated as one object. For example the following is @@ -106,7 +105,7 @@ class CallLiteral extends Call { class DisjunctionPredicateLiteral extends DisjunctionChain { DisjunctionPredicateLiteral() { // Call to the same target - exists(PredicateOrBuiltin target | + exists(AstNode target | forex(Formula f | f = getAnOperandRec() | f.(CallLiteral).getTarget() = target) ) } From 6af28e37ae8cefbc4c3876a675c0d9f8d4ed24c4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:22:49 +0100 Subject: [PATCH 10/43] We can use PredicateOrBuiltin now. --- ql/src/queries/style/UseSetLiteral.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/queries/style/UseSetLiteral.ql b/ql/src/queries/style/UseSetLiteral.ql index 0481ac39299..d0e983a1774 100644 --- a/ql/src/queries/style/UseSetLiteral.ql +++ b/ql/src/queries/style/UseSetLiteral.ql @@ -105,7 +105,7 @@ class CallLiteral extends Call { class DisjunctionPredicateLiteral extends DisjunctionChain { DisjunctionPredicateLiteral() { // Call to the same target - exists(AstNode target | + exists(PredicateOrBuiltin target | forex(Formula f | f = getAnOperandRec() | f.(CallLiteral).getTarget() = target) ) } From d23de3dcd8e059f1dadefd5f681e7ca0f90aa10e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 14 Oct 2021 14:48:16 +0200 Subject: [PATCH 11/43] make another codeql-action workflow that uses a published pack --- ...lysis.yml => bleeding-codeql-analysis.yml} | 2 +- .../workflows/published-codeql-analysis.yml | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) rename .github/workflows/{codeql-analysis.yml => bleeding-codeql-analysis.yml} (98%) create mode 100644 .github/workflows/published-codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/bleeding-codeql-analysis.yml similarity index 98% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/bleeding-codeql-analysis.yml index 664bfde89bf..b9b45afd0f0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/bleeding-codeql-analysis.yml @@ -1,4 +1,4 @@ -name: "CodeQL" +name: "CodeQL with bleeding edge queries and extractor" on: workflow_dispatch: diff --git a/.github/workflows/published-codeql-analysis.yml b/.github/workflows/published-codeql-analysis.yml new file mode 100644 index 00000000000..10ce6d04623 --- /dev/null +++ b/.github/workflows/published-codeql-analysis.yml @@ -0,0 +1,49 @@ +name: "CodeQL with published queries and extractor" + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + +jobs: + + analyze: + name: Analyze + + runs-on: ubuntu-latest + + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Download pack + run: | + gh release download latest --pattern ql-qlpack.zip + unzip ql-qlpack.zip -d "${PACK}" + env: + PACK: ${{ runner.temp }}/ql-qlpack + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Hack codeql-action options + run: | + JSON=$(jq -nc --arg pack "${PACK}" '.resolve.queries=["--search-path", $pack] | .resolve.extractor=["--search-path", $pack] | .database.init=["--search-path", $pack]') + echo "CODEQL_ACTION_EXTRA_OPTIONS=${JSON}" >> ${GITHUB_ENV} + env: + PACK: ${{ runner.temp }}/ql-qlpack + + - name: Initialize CodeQL + uses: github/codeql-action/init@esbena/ql + with: + languages: ql + db-location: ${{ runner.temp }}/db + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@esbena/ql From 76880e8f9346a35962cd7372b8f4ea9fe347fea0 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:31:42 +0100 Subject: [PATCH 12/43] Autoformat and fix test. --- .../UseSetLiteral/UseSetLiteral.expected | 16 +- ql/test/queries/style/UseSetLiteral/test.qll | 176 +++++++++--------- 2 files changed, 100 insertions(+), 92 deletions(-) diff --git a/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected index 053a8b55512..fac79ff078e 100644 --- a/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected +++ b/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected @@ -1,8 +1,8 @@ -| test.qll:4:2:7:6 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | -| test.qll:29:2:32:9 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | -| test.qll:43:2:46:11 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | -| test.qll:62:4:65:11 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | -| test.qll:67:4:70:10 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | -| test.qll:72:4:75:10 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | -| test.qll:89:2:92:8 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | -| test.qll:126:2:126:37 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:4:3:7:7 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:30:3:33:10 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | +| test.qll:44:3:47:12 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:62:7:65:14 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:68:7:71:13 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:74:7:77:13 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | +| test.qll:87:3:90:9 | Disjunction | This formula of 4 predicate calls can be replaced with a single call on a set literal, improving readability. | +| test.qll:128:3:134:3 | Disjunction | This formula of 4 comparisons can be replaced with a single equality on a set literal, improving readability. | diff --git a/ql/test/queries/style/UseSetLiteral/test.qll b/ql/test/queries/style/UseSetLiteral/test.qll index 5016e032c11..36a5f938f89 100644 --- a/ql/test/queries/style/UseSetLiteral/test.qll +++ b/ql/test/queries/style/UseSetLiteral/test.qll @@ -1,127 +1,135 @@ import ql predicate test1(int a) { - a = 1 or // BAD - a = 2 or - a = 3 or - a = 4 + a = 1 or // BAD + a = 2 or + a = 3 or + a = 4 } predicate test2(int a) { - a = [1, 2, 3, 4] // GOOD + a = [1, 2, 3, 4] // GOOD } predicate test3(int a) { - a = 1 and // GOOD (for the purposes of this query) - a = 2 and - a = 3 and - a = 4 + a = 1 and // GOOD (for the purposes of this query) + a = 2 and + a = 3 and + a = 4 } -bindingset[a] predicate test4(int a) { - a < 1 or // GOOD (for the purposes of this query) - a = 2 or - a >= 3 or - a > 4 +bindingset[a] +predicate test4(int a) { + a < 1 or // GOOD (for the purposes of this query) + a = 2 or + a >= 3 or + a > 4 } predicate test5() { - test1(1) or // BAD - test1(2) or - test1(3) or - test1(4) + test1(1) or // BAD + test1(2) or + test1(3) or + test1(4) } predicate test6() { - test1(1) or // GOOD - test2(2) or - test3(3) or - test4(4) + test1(1) or // GOOD + test2(2) or + test3(3) or + test4(4) } int test7() { - 1 = result or // BAD - 2 = result or - 3 = result or - 4 = result + 1 = result or // BAD + 2 = result or + 3 = result or + 4 = result } predicate test8() { - test7() = 1 or // BAD [NOT DETECTED] - test7() = 2 or - test7() = 3 or - test7() = 4 + test7() = 1 or // BAD [NOT DETECTED] + test7() = 2 or + test7() = 3 or + test7() = 4 } -class MyTest8Class extends int -{ - string s; +class MyTest8Class extends int { + string s; - MyTest8Class() { - ( - this = 1 or // BAD - this = 2 or - this = 3 or - this = 4 - ) and ( - s = "1" or // BAD - s = "2" or - s = "3" or - s = "4" - ) and exists(float f | - f = 1.0 or // BAD - f = 1.5 or - f = 2.0 or - f = 2.5 - ) - } + MyTest8Class() { + ( + this = 1 or // BAD + this = 2 or + this = 3 or + this = 4 + ) and + ( + s = "1" or // BAD + s = "2" or + s = "3" or + s = "4" + ) and + exists(float f | + f = 1.0 or // BAD + f = 1.5 or + f = 2.0 or + f = 2.5 + ) + } - predicate is(int x) { - x = this - } + predicate is(int x) { x = this } - int get() { - result = this - } + int get() { result = this } } predicate test9(MyTest8Class c) { - c.is(1) or // BAD - c.is(2) or - c.is(3) or - c.is(4) + c.is(1) or // BAD + c.is(2) or + c.is(3) or + c.is(4) } predicate test10(MyTest8Class c) { - c.get() = 1 or // BAD [NOT DETECTED] - c.get() = 2 or - c.get() = 3 or - c.get() = 4 + c.get() = 1 or // BAD [NOT DETECTED] + c.get() = 2 or + c.get() = 3 or + c.get() = 4 } -bindingset[a, b, c, d] predicate test11(int a, int b, int c, int d) { - a = 1 or // GOOD - b = 2 or - c = 3 or - d = 4 +bindingset[a, b, c, d] +predicate test11(int a, int b, int c, int d) { + a = 1 or // GOOD + b = 2 or + c = 3 or + d = 4 } -bindingset[a, b] predicate test12(int a, int b) { - a = 1 or // BAD [NOT DETECTED] - a = 2 or - a = 3 or - a = 4 or - b = 0 +bindingset[a, b] +predicate test12(int a, int b) { + a = 1 or // BAD [NOT DETECTED] + a = 2 or + a = 3 or + a = 4 or + b = 0 } predicate test13(int a, int b) { - (a = 1 and b = 1) or // GOOD - (a = 2 and b = 4) or - (a = 3 and b = 9) or - (a = 4 and b = 16) + a = 1 and b = 1 // GOOD + or + a = 2 and b = 4 + or + a = 3 and b = 9 + or + a = 4 and b = 16 } -from int a -where - a = 1 or ((a = 2 or a = 3) or a = 4) // BAD -select a +predicate test14(int a) { + a = 1 // BAD + or + ( + (a = 2 or a = 3) + or + a = 4 + ) +} From ab11bce776d34e5a8b8e6eb00a534cc37805dbb7 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 14 Oct 2021 15:34:54 +0200 Subject: [PATCH 13/43] document usage --- .github/workflows/published-codeql-analysis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/published-codeql-analysis.yml b/.github/workflows/published-codeql-analysis.yml index 10ce6d04623..706be102d56 100644 --- a/.github/workflows/published-codeql-analysis.yml +++ b/.github/workflows/published-codeql-analysis.yml @@ -26,6 +26,10 @@ jobs: - name: Download pack run: | + # adjust this line to make the workflow work in other repositories + # the ql-qlpack.zip file can be downloaded at: + # - https://github.com/github/codeql-ql/releases + # - https://github.com/github/codeql-ql/actions/workflows/bleeding-codeql-analysis.yml gh release download latest --pattern ql-qlpack.zip unzip ql-qlpack.zip -d "${PACK}" env: From ec292dbffdf763caed2d86a0b7bcf86af95b0300 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 14 Oct 2021 15:42:40 +0200 Subject: [PATCH 14/43] New performance query: Transitive step in recursion. --- ql/src/queries/performance/TransitiveStep.ql | 165 +++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 ql/src/queries/performance/TransitiveStep.ql diff --git a/ql/src/queries/performance/TransitiveStep.ql b/ql/src/queries/performance/TransitiveStep.ql new file mode 100644 index 00000000000..045424256db --- /dev/null +++ b/ql/src/queries/performance/TransitiveStep.ql @@ -0,0 +1,165 @@ +/** + * @name Transitively closed recursive delta + * @description Using a transitively closed relation as the step in a recursive + * delta can perform poorly as it is inherently quadratic and may + * force materialization of a fastTC. The transitively closed delta + * can usually just be replaced by the underlying step relation as + * the recursive context will provide transitive closure. + * @kind problem + * @problem.severity error + * @id ql/transitive-step + * @tags performance + * @precision high + */ + +import ql + +Expr getArg(Call c, int i) { + result = c.getArgument(i) + or + result = c.(MemberCall).getBase() and i = -1 + or + exists(c.getType()) and result = c and i = -2 +} + +newtype TParameter = + TThisParam(ClassPredicate p) or + TResultParam(Predicate p) { exists(p.getReturnType()) } or + TVarParam(VarDecl v) { any(Predicate p).getParameter(_) = v } + +class Parameter extends TParameter { + string toString() { + this instanceof TThisParam and result = "this" + or + this instanceof TResultParam and result = "result" + or + exists(VarDecl v | this = TVarParam(v) and result = v.toString()) + } + + Expr getAnAccess() { + result instanceof ThisAccess and this = TThisParam(result.getEnclosingPredicate()) + or + result instanceof ResultAccess and this = TResultParam(result.getEnclosingPredicate()) + or + this = TVarParam(result.(VarAccess).getDeclaration()) + } + + predicate isParameterOf(Predicate p, int i) { + this = TThisParam(p) and i = -1 + or + this = TResultParam(p) and i = -2 + or + this = TVarParam(p.getParameter(i)) + } +} + +predicate hasTwoArgs(Call c, Expr arg1, Expr arg2) { + exists(int i1, int i2 | + arg1 = getArg(c, i1) and + arg2 = getArg(c, i2) and + i1 != i2 and + strictcount(getArg(c, _)) = 2 + ) +} + +predicate transitivePred(Predicate p, AstNode tc) { + exists(PredicateExpr pe | + p.(ClasslessPredicate).getAlias() = pe and + transitivePred(pe.getResolvedPredicate(), tc) + ) + or + p.(ClasslessPredicate).getAlias().(HigherOrderFormula).getName() = "fastTC" and + tc = p + or + strictcount(Parameter par | par.isParameterOf(p, _)) = 2 and + exists(Formula body | p.getBody() = body | + transitiveCall(body, tc) and + hasTwoArgs(body, any(Identifier i1), any(Identifier i2)) + or + exists(ComparisonFormula eq, Call c | + body = eq and + eq.getSymbol() = "=" and + transitiveCall(c, tc) and + getArg(c, _) instanceof Identifier and + eq.getAnOperand() = c and + eq.getAnOperand() instanceof Identifier + ) + ) +} + +predicate transitiveCall(Call c, AstNode tc) { + c.isClosure(_) and tc = c + or + transitivePred(c.getTarget(), tc) +} + +class TransitivelyClosedCall extends Call { + TransitivelyClosedCall() { transitiveCall(this, _) } + + predicate hasArgs(Expr arg1, Expr arg2) { hasTwoArgs(this, arg1, arg2) } + + AstNode getReason() { transitiveCall(this, result) } +} + +AstNode getParentOfExpr(Expr e) { result = e.getParent() } + +Formula getEnclosing(Expr e) { result = getParentOfExpr+(e) } + +Formula enlargeScopeStep(Formula f) { result.(Conjunction).getAnOperand() = f } + +Formula enlargeScope(Formula f) { + result = enlargeScopeStep*(f) and not exists(enlargeScopeStep(result)) +} + +predicate varaccesValue(VarAccess va, VarDecl v, Formula scope) { + va.getDeclaration() = v and + scope = enlargeScope(getEnclosing(va)) +} + +predicate thisValue(ThisAccess ta, Formula scope) { scope = enlargeScope(getEnclosing(ta)) } + +predicate resultValue(ResultAccess ra, Formula scope) { scope = enlargeScope(getEnclosing(ra)) } + +predicate valueStep(Expr e1, Expr e2) { + exists(VarDecl v, Formula scope | + varaccesValue(e1, v, scope) and + varaccesValue(e2, v, scope) + ) + or + exists(Formula scope | + thisValue(e1, scope) and + thisValue(e2, scope) + or + resultValue(e1, scope) and + resultValue(e2, scope) + ) + or + exists(InlineCast c | + e1 = c and e2 = c.getBase() + or + e2 = c and e1 = c.getBase() + ) + or + exists(ComparisonFormula eq | + eq.getSymbol() = "=" and + eq.getAnOperand() = e1 and + eq.getAnOperand() = e2 and + e1 != e2 + ) +} + +predicate transitiveDelta(Call rec, TransitivelyClosedCall tc) { + exists(Expr recarg, int i, Expr tcarg, Predicate pred, Parameter p | + rec.getTarget() = pred and + pred = rec.getEnclosingPredicate() and + recarg = getArg(rec, i) and + valueStep*(recarg, tcarg) and + tc.hasArgs(tcarg, p.getAnAccess()) and + p.isParameterOf(pred, i) + ) +} + +from Call rec, TransitivelyClosedCall tc, AstNode reason +where transitiveDelta(rec, tc) and reason = tc.getReason() +select tc, "This recursive delta is transively closed $@, which may be a performance problem.", + reason, "here" From ec6a8b933c3639fa0eddba7feab88809d305978e Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 14 Oct 2021 14:36:14 +0100 Subject: [PATCH 15/43] Query for finding missing or unwanted bidirectional imports of abstract classes --- ql/src/codeql_ql/ast/Ast.qll | 3 + ql/src/codeql_ql/ast/internal/Module.qll | 8 +- .../performance/AbstractClassImport.ql | 82 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 ql/src/queries/performance/AbstractClassImport.ql diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 2c93da2af47..69b41b35188 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -804,6 +804,9 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { result = this.getClassPredicate(name) ) } + + /** Holds if this class is abstract. */ + predicate isAbstract() { hasAnnotation(this, "abstract") } } /** diff --git a/ql/src/codeql_ql/ast/internal/Module.qll b/ql/src/codeql_ql/ast/internal/Module.qll index a64ccfd0f2d..9cae791e410 100644 --- a/ql/src/codeql_ql/ast/internal/Module.qll +++ b/ql/src/codeql_ql/ast/internal/Module.qll @@ -19,7 +19,9 @@ private class ContainerOrModule extends TContainerOrModule { private class TFileOrModule = TFile or TModule; /** A file or a module. */ -class FileOrModule extends TFileOrModule, ContainerOrModule { } +class FileOrModule extends TFileOrModule, ContainerOrModule { + abstract File getFile(); +} private class File_ extends FileOrModule, TFile { File f; @@ -41,6 +43,8 @@ private class File_ extends FileOrModule, TFile { endline = 0 and endcolumn = 0 } + + override File getFile() { result = f } } private class Folder_ extends ContainerOrModule, TFolder { @@ -90,6 +94,8 @@ class Module_ extends FileOrModule, TModule { ) { m.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + + override File getFile() { result = m.getLocation().getFile() } } private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) { diff --git a/ql/src/queries/performance/AbstractClassImport.ql b/ql/src/queries/performance/AbstractClassImport.ql new file mode 100644 index 00000000000..bccad253561 --- /dev/null +++ b/ql/src/queries/performance/AbstractClassImport.ql @@ -0,0 +1,82 @@ +/** + * @name Bidirectional imports for abstract classes + * @description An abstract class should import each of its subclasses, unless it is meant as an extension point, in which case it should import none of them. + * @kind problem + * @problem.severity error + * @id ql/abstract-class-import + * @tags correctness + * performance + * @precision high + */ + +import ql +import codeql_ql.ast.internal.Module + +File imports(File file) { + exists(Import imp | + imp.getLocation().getFile() = file and + result = imp.getResolvedModule().getFile() + ) +} + +/** Gets a non-abstract subclass of `ab` that is defined in a different file */ +Class concreteExternalSubclass(Class ab) { + ab.isAbstract() and + not result.isAbstract() and + result.getType().getASuperType*() = ab.getType() and + result.getLocation().getFile() != ab.getLocation().getFile() +} + +/** Holds if there is a bidirectional import between the abstract class `ab` and its subclass `sub` */ +predicate bidirectionalImport(Class ab, Class sub) { + sub = concreteExternalSubclass(ab) and + sub.getLocation().getFile() = imports*(ab.getLocation().getFile()) +} + +predicate stats(Class ab, int imports, int nonImports) { + ab.isAbstract() and + imports = + strictcount(Class sub | sub = concreteExternalSubclass(ab) and bidirectionalImport(ab, sub)) and + nonImports = + strictcount(Class sub | sub = concreteExternalSubclass(ab) and not bidirectionalImport(ab, sub)) +} + +predicate alert(Class ab, string msg, Class sub, Class sub2) { + sub = concreteExternalSubclass(ab) and + sub2 = concreteExternalSubclass(ab) and + exists(int imports, int nonImports | stats(ab, imports, nonImports) | + (imports < 10 or nonImports < 10) and // if this is not the case, it's likely intended + ( + // report whichever of imports or nonimports there are more of; both if equal + imports >= nonImports and + not bidirectionalImport(ab, sub) and + sub2 = + min(Class other | + other = concreteExternalSubclass(ab) and + bidirectionalImport(ab, other) + | + other order by other.getLocation().toString() + ) and + msg = + "This abstract class doesn't import its subclass $@ but imports " + imports + + " other subclasses, such as $@." + or + nonImports >= imports and + bidirectionalImport(ab, sub) and + sub2 = + min(Class other | + other = concreteExternalSubclass(ab) and + not bidirectionalImport(ab, other) + | + other order by other.getLocation().toString() + ) and + msg = + "This abstract class imports its subclass $@ but doesn't import " + nonImports + + " other subclasses, such as $@." + ) + ) +} + +from Class ab, string msg, Class sub, Class sub2 +where alert(ab, msg, sub, sub2) +select ab, msg, sub, sub.getName(), sub2, sub2.getName() From 9b786c27c0d5f2a484b36d39fed7e76abd26e47f Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 14 Oct 2021 15:03:19 +0100 Subject: [PATCH 16/43] Fix isAbstract --- ql/src/codeql_ql/ast/Ast.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 69b41b35188..6fe8945997c 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -806,7 +806,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { } /** Holds if this class is abstract. */ - predicate isAbstract() { hasAnnotation(this, "abstract") } + predicate isAbstract() { hasAnnotation("abstract") } } /** From 0a3705b7afac5473e3f8dab49d14c8441e5ca4f6 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:36:58 +0100 Subject: [PATCH 17/43] Add ql/missing-qldoc query. --- ql/src/queries/style/docs/MissingQLDoc.ql | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 ql/src/queries/style/docs/MissingQLDoc.ql diff --git a/ql/src/queries/style/docs/MissingQLDoc.ql b/ql/src/queries/style/docs/MissingQLDoc.ql new file mode 100644 index 00000000000..c7da02073a3 --- /dev/null +++ b/ql/src/queries/style/docs/MissingQLDoc.ql @@ -0,0 +1,25 @@ +/** + * @name Missing QLDoc. + * @description Library classes should have QLDoc. + * @kind problem + * @problem.severity recommendation + * @id ql/missing-qldoc + * @tags maintainability + * @precision high + */ + +import ql + +from File f, Class c +where + f = c.getLocation().getFile() and + not exists(c.getQLDoc()) and // no QLDoc + f.getExtension() = "qll" and // in a library + not c.isPrivate() and // class is public + not exists(Module m | + m.getAMember*() = c and + m.isPrivate() // modules containing the class are public + ) and + not exists(c.getAliasType()) and // class is not just an alias + not f.getParentContainer*().getBaseName().toLowerCase() = ["internal", "experimental", "test"] // exclusions +select c, "This library class should have QLDoc." From c6a52ed2eaed308896e37df8e8bbf5e02d01b68f Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 14 Oct 2021 15:44:23 +0000 Subject: [PATCH 18/43] Query: Noninitial imports of the standard library Finds a single result in ``` semmle.code.java.dataflow.internal.rangeanalysis.SignAnalysisSpecific.qll ``` which starts with ```ql module Private { import semmle.code.java.dataflow.RangeUtils as RU private import semmle.code.java.dataflow.SSA as Ssa private import semmle.code.java.controlflow.Guards as G private import java as J private import Sign ... ``` --- .../performance/NonInitialStdLibImport.ql | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 ql/src/queries/performance/NonInitialStdLibImport.ql diff --git a/ql/src/queries/performance/NonInitialStdLibImport.ql b/ql/src/queries/performance/NonInitialStdLibImport.ql new file mode 100644 index 00000000000..831e1df789d --- /dev/null +++ b/ql/src/queries/performance/NonInitialStdLibImport.ql @@ -0,0 +1,30 @@ +/** + * @name Standard library is not the first import + * @description Importing other libraries before the standard library can cause a change in + * evaluation order and may lead to performance errors. + * @kind problem + * @problem.severity error + * @id ql/noninitial-stdlib-import + * @tags performance + * @precision high + */ + +import ql + +predicate isStdLibImport(Import i, string name) { + name = i.getQualifiedName(0) and + i.getLocation().getFile().getRelativePath().matches(name + "%") and + not exists(i.getQualifiedName(1)) +} + +Import importBefore(Import i) { + exists(Module m, int bi, int ii | + result = m.getMember(bi) and + i = m.getMember(ii) and + bi < ii + ) +} + +from Import i +where isStdLibImport(i, _) and exists(importBefore(i)) +select i, "This import may cause reevaluation to occur, as there are other imports preceding it" From 71f69997e29bc6308813b996dda817c1fa767923 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 14 Oct 2021 16:06:19 +0000 Subject: [PATCH 19/43] Autoformat --- ql/src/queries/performance/NonInitialStdLibImport.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/queries/performance/NonInitialStdLibImport.ql b/ql/src/queries/performance/NonInitialStdLibImport.ql index 831e1df789d..88296d895fc 100644 --- a/ql/src/queries/performance/NonInitialStdLibImport.ql +++ b/ql/src/queries/performance/NonInitialStdLibImport.ql @@ -22,7 +22,7 @@ Import importBefore(Import i) { result = m.getMember(bi) and i = m.getMember(ii) and bi < ii - ) + ) } from Import i From 016ff2af63d0032e60e5349d6ea8a4773033d84c Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 14 Oct 2021 22:37:17 +0200 Subject: [PATCH 20/43] fix implicit this --- ql/src/codeql/files/FileSystem.qll | 24 ++++--- ql/src/codeql_ql/ast/Ast.qll | 88 +++++++++++++------------- ql/src/codeql_ql/ast/internal/Type.qll | 16 ++--- 3 files changed, 67 insertions(+), 61 deletions(-) diff --git a/ql/src/codeql/files/FileSystem.qll b/ql/src/codeql/files/FileSystem.qll index 7573ff9e905..98d8c3142ea 100644 --- a/ql/src/codeql/files/FileSystem.qll +++ b/ql/src/codeql/files/FileSystem.qll @@ -9,10 +9,10 @@ abstract class Container extends @container { Container getAChildContainer() { this = result.getParentContainer() } /** Gets a file in this container. */ - File getAFile() { result = getAChildContainer() } + File getAFile() { result = this.getAChildContainer() } /** Gets a sub-folder in this container. */ - Folder getAFolder() { result = getAChildContainer() } + Folder getAFolder() { result = this.getAChildContainer() } /** * Gets the absolute, canonical path of this container, using forward slashes @@ -58,7 +58,7 @@ abstract class Container extends @container { * */ string getBaseName() { - result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) + result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) } /** @@ -84,17 +84,19 @@ abstract class Container extends @container { * "/tmp/x.tar.gz""gz" * */ - string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) } + string getExtension() { + result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) + } /** Gets the file in this container that has the given `baseName`, if any. */ File getFile(string baseName) { - result = getAFile() and + result = this.getAFile() and result.getBaseName() = baseName } /** Gets the sub-folder in this container that has the given `baseName`, if any. */ Folder getFolder(string baseName) { - result = getAFolder() and + result = this.getAFolder() and result.getBaseName() = baseName } @@ -111,7 +113,7 @@ abstract class Container extends @container { */ string getRelativePath() { exists(string absPath, string pref | - absPath = getAbsolutePath() and sourceLocationPrefix(pref) + absPath = this.getAbsolutePath() and sourceLocationPrefix(pref) | absPath = pref and result = "" or @@ -137,7 +139,9 @@ abstract class Container extends @container { * "/tmp/x.tar.gz""x.tar" * */ - string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) } + string getStem() { + result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) + } /** * Gets a URL representing the location of this container. @@ -151,7 +155,7 @@ abstract class Container extends @container { * * This is the absolute path of the container. */ - string toString() { result = getAbsolutePath() } + string toString() { result = this.getAbsolutePath() } } /** A folder. */ @@ -159,7 +163,7 @@ class Folder extends Container, @folder { override string getAbsolutePath() { folders(this, result, _) } /** Gets the URL of this folder. */ - override string getURL() { result = "folder://" + getAbsolutePath() } + override string getURL() { result = "folder://" + this.getAbsolutePath() } } /** A file. */ diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 2c93da2af47..748b95f0b45 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -19,7 +19,7 @@ private string stringIndexedMember(string name, string index) { /** An AST node of a QL program */ class AstNode extends TAstNode { - string toString() { result = getAPrimaryQlClass() } + string toString() { result = this.getAPrimaryQlClass() } /** * Gets the location of the AST node. @@ -35,8 +35,8 @@ class AstNode extends TAstNode { predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - if exists(getLocation()) - then getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + if exists(this.getLocation()) + then this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) else ( filepath = "" and startline = 0 and @@ -92,7 +92,7 @@ class TopLevel extends TTopLevel, AstNode { * Gets a member from contained in this top-level module. * Includes private members. */ - ModuleMember getAMember() { result = getMember(_) } + ModuleMember getAMember() { result = this.getMember(_) } /** Gets the `i`'th member of this top-level module. */ ModuleMember getMember(int i) { @@ -201,13 +201,13 @@ class PredicateOrBuiltin extends TPredOrBuiltin, AstNode { Type getReturnType() { none() } - int getArity() { result = count(getParameterType(_)) } + int getArity() { result = count(this.getParameterType(_)) } predicate isPrivate() { none() } } class BuiltinPredicate extends PredicateOrBuiltin, TBuiltin { - override string toString() { result = getName() } + override string toString() { result = this.getName() } override string getAPrimaryQlClass() { result = "BuiltinPredicate" } } @@ -268,7 +268,7 @@ class Predicate extends TPredicate, AstNode, PredicateOrBuiltin, Declaration { */ override int getArity() { not this.(ClasslessPredicate).getAlias() instanceof PredicateExpr and - result = count(getParameter(_)) + result = count(this.getParameter(_)) or exists(PredicateExpr alias | alias = this.(ClasslessPredicate).getAlias() | result = alias.getArity() @@ -278,7 +278,7 @@ class Predicate extends TPredicate, AstNode, PredicateOrBuiltin, Declaration { /** * Holds if this predicate is private. */ - override predicate isPrivate() { hasAnnotation("private") } + override predicate isPrivate() { this.hasAnnotation("private") } /** * Gets the return type (if any) of the predicate. @@ -325,18 +325,18 @@ class Relation extends TDBRelation, AstNode, Declaration { } /** Gets the `i`th parameter name */ - string getParameterName(int i) { result = getColumn(i).getColName().getValue() } + string getParameterName(int i) { result = this.getColumn(i).getColName().getValue() } /** Gets the `i`th parameter type */ string getParameterType(int i) { // TODO: This is just using the name of the type, not the actual type. Checkout Type.qll - result = getColumn(i).getColType().getChild().(Generated::Token).getValue() + result = this.getColumn(i).getColType().getChild().(Generated::Token).getValue() } /** * Gets the number of parameters. */ - int getArity() { result = count(getColumn(_)) } + int getArity() { result = count(this.getColumn(_)) } override string getAPrimaryQlClass() { result = "Relation" } } @@ -472,7 +472,7 @@ class ClassPredicate extends TClassPredicate, Predicate { /** * Holds if this predicate is annotated as overriding another predicate. */ - predicate isOverride() { hasAnnotation("override") } + predicate isOverride() { this.hasAnnotation("override") } override VarDecl getParameter(int i) { toGenerated(result) = @@ -486,7 +486,7 @@ class ClassPredicate extends TClassPredicate, Predicate { /** * Gets the type representing this class. */ - override ClassType getDeclaringType() { result.getDeclaration() = getParent() } + override ClassType getDeclaringType() { result.getDeclaration() = this.getParent() } predicate overrides(ClassPredicate other) { predOverrides(this, other) } @@ -515,7 +515,7 @@ class CharPred extends TCharPred, Predicate { override Formula getBody() { toGenerated(result) = pred.getBody() } - override string getName() { result = getParent().(Class).getName() } + override string getName() { result = this.getParent().(Class).getName() } override AstNode getAChild(string pred_name) { result = super.getAChild(pred_name) @@ -523,7 +523,7 @@ class CharPred extends TCharPred, Predicate { pred_name = directMember("getBody") and result = this.getBody() } - override ClassType getDeclaringType() { result.getDeclaration() = getParent() } + override ClassType getDeclaringType() { result.getDeclaration() = this.getParent() } } /** @@ -685,7 +685,7 @@ class Module extends TModule, ModuleDeclaration { */ class ModuleMember extends TModuleMember, AstNode { /** Holds if this member is declared as `private`. */ - predicate isPrivate() { hasAnnotation("private") } + predicate isPrivate() { this.hasAnnotation("private") } } /** A declaration. E.g. a class, type, predicate, newtype... */ @@ -756,7 +756,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * Gets predicate `name` implemented in this class. */ ClassPredicate getClassPredicate(string name) { - result = getAClassPredicate() and + result = this.getAClassPredicate() and result.getName() = name } @@ -894,7 +894,7 @@ class Call extends TCall, Expr, Formula { } /** Gets an argument of this call, if any. */ - final Expr getAnArgument() { result = getArgument(_) } + final Expr getAnArgument() { result = this.getArgument(_) } PredicateOrBuiltin getTarget() { resolveCall(this, result) } @@ -1271,7 +1271,7 @@ class ComparisonFormula extends TComparisonFormula, Formula { Expr getRightOperand() { toGenerated(result) = comp.getRight() } /** Gets an operand of this comparison. */ - Expr getAnOperand() { result in [getLeftOperand(), getRightOperand()] } + Expr getAnOperand() { result in [this.getLeftOperand(), this.getRightOperand()] } /** Gets the operator of this comparison. */ ComparisonOp getOperator() { toGenerated(result) = comp.getChild() } @@ -1329,7 +1329,7 @@ class Quantifier extends TQuantifier, Formula { * Holds if this is the "expression only" form of an exists quantifier. * In other words, the quantifier is of the form `exists( expr )`. */ - predicate hasExpr() { exists(getExpr()) } + predicate hasExpr() { exists(this.getExpr()) } override string getAPrimaryQlClass() { result = "Quantifier" } @@ -1501,7 +1501,7 @@ class HigherOrderFormula extends THigherOrderFormula, Formula { * Gets the `i`th argument to this higher-order formula. * E.g. for `fastTC(pathSucc/2)(n1, n2)` the result is `n1` and `n2`. */ - Expr getArgument(int i) { toGenerated(result) = hop.getChild(i + getNumInputs()) } + Expr getArgument(int i) { toGenerated(result) = hop.getChild(i + this.getNumInputs()) } /** * Gets the name of this higher-order predicate. @@ -1590,7 +1590,7 @@ class ExprAggregate extends TExprAggregate, Aggregate { ) or not kind = ["count", "strictcount"] and - result = getExpr(0).getType() + result = this.getExpr(0).getType() } } @@ -1654,11 +1654,11 @@ class FullAggregate extends TFullAggregate, Aggregate { ) or kind = ["any", "min", "max", "unique"] and - not exists(getExpr(_)) and - result = getArgument(0).getTypeExpr().getResolvedType() + not exists(this.getExpr(_)) and + result = this.getArgument(0).getTypeExpr().getResolvedType() or not kind = ["count", "strictcount"] and - result = getExpr(0).getType() + result = this.getExpr(0).getType() } override AstNode getAChild(string pred) { @@ -1882,7 +1882,7 @@ class BinOpExpr extends TBinOpExpr, Expr { FunctionSymbol getOperator() { none() } // overriden in subclasses /* Gets an operand of the binary expression. */ - final Expr getAnOperand() { result = getLeftOperand() or result = getRightOperand() } + final Expr getAnOperand() { result = this.getLeftOperand() or result = this.getRightOperand() } } /** @@ -2039,7 +2039,7 @@ class Range extends TRange, Expr { /** * Gets the lower and upper bounds of the range. */ - Expr getAnEndpoint() { result = [getLowEndpoint(), getHighEndpoint()] } + Expr getAnEndpoint() { result = [this.getLowEndpoint(), this.getHighEndpoint()] } override PrimitiveType getType() { result.getName() = "int" } @@ -2070,12 +2070,12 @@ class Set extends TSet, Expr { /** * Gets an element in this set literal expression, if any. */ - Expr getAnElement() { result = getElement(_) } + Expr getAnElement() { result = this.getElement(_) } /** * Gets the number of elements in this set literal expression. */ - int getNumberOfElements() { result = count(getAnElement()) } + int getNumberOfElements() { result = count(this.getAnElement()) } override Type getType() { result = this.getElement(0).getType() } @@ -2084,7 +2084,7 @@ class Set extends TSet, Expr { override AstNode getAChild(string pred) { result = super.getAChild(pred) or - exists(int i | pred = indexedMember("getElement", i) and result = getElement(i)) + exists(int i | pred = indexedMember("getElement", i) and result = this.getElement(i)) } } @@ -2267,10 +2267,10 @@ class BindingSet extends Annotation { string getBoundName(int index) { result = this.getArgs(index).getValue() } /** Gets a name bound by this bindingset, if any. */ - string getABoundName() { result = getBoundName(_) } + string getABoundName() { result = this.getBoundName(_) } /** Gets the number of names bound by this bindingset. */ - int getNumberOfBoundNames() { result = count(getABoundName()) } + int getNumberOfBoundNames() { result = count(this.getABoundName()) } } /** @@ -2280,7 +2280,7 @@ module YAML { /** A node in a YAML file */ class YAMLNode extends TYAMLNode, AstNode { /** Holds if the predicate is a root node (has no parent) */ - predicate isRoot() { not exists(getParent()) } + predicate isRoot() { not exists(this.getParent()) } } /** A YAML comment. */ @@ -2349,7 +2349,7 @@ module YAML { * Dashes are replaced with `/` (because we don't have that information in the generated AST). */ string getQualifiedName() { - result = concat(string part, int i | part = getNamePart(i) | part, "/" order by i) + result = concat(string part, int i | part = this.getNamePart(i) | part, "/" order by i) } } @@ -2402,15 +2402,15 @@ module YAML { } /** Gets the name of this qlpack */ - string getName() { result = getProperty("name") } + string getName() { result = this.getProperty("name") } /** Gets the version of this qlpack */ - string getVersion() { result = getProperty("version") } + string getVersion() { result = this.getProperty("version") } /** Gets the extractor of this qlpack */ - string getExtractor() { result = getProperty("extractor") } + string getExtractor() { result = this.getProperty("extractor") } - string toString() { result = getName() } + string toString() { result = this.getName() } /** Gets the file that this `QLPack` represents. */ File getFile() { result = file } @@ -2425,7 +2425,7 @@ module YAML { entry.getLocation().getStartColumn() > deps.getLocation().getStartColumn() ) or - exists(YAMLEntry prev | isADependency(prev) | + exists(YAMLEntry prev | this.isADependency(prev) | prev.getLocation().getFile() = file and entry.getLocation().getFile() = file and entry.getLocation().getStartLine() = 1 + prev.getLocation().getStartLine() and @@ -2434,7 +2434,7 @@ module YAML { } predicate hasDependency(string name, string version) { - exists(YAMLEntry entry | isADependency(entry) | + exists(YAMLEntry entry | this.isADependency(entry) | entry.getKey().getQualifiedName() = name and entry.getValue().getValue() = version ) @@ -2442,7 +2442,7 @@ module YAML { /** Gets the database scheme of this qlpack */ File getDBScheme() { - result.getBaseName() = getProperty("dbscheme") and + result.getBaseName() = this.getProperty("dbscheme") and result = file.getParentContainer().getFile(any(string s | s.matches("%.dbscheme"))) } @@ -2450,14 +2450,16 @@ module YAML { Container getAFileInPack() { result.getParentContainer() = file.getParentContainer() or - result = getAFileInPack().(Folder).getAChildContainer() + result = this.getAFileInPack().(Folder).getAChildContainer() } /** * Gets a QLPack that this QLPack depends on. */ QLPack getADependency() { - exists(string name | hasDependency(name, _) | result.getName().replaceAll("-", "/") = name) + exists(string name | this.hasDependency(name, _) | + result.getName().replaceAll("-", "/") = name + ) } Location getLocation() { diff --git a/ql/src/codeql_ql/ast/internal/Type.qll b/ql/src/codeql_ql/ast/internal/Type.qll index ee4c5dc8400..8449b08de4c 100644 --- a/ql/src/codeql_ql/ast/internal/Type.qll +++ b/ql/src/codeql_ql/ast/internal/Type.qll @@ -27,7 +27,7 @@ private predicate isActualClass(Class c) { * A type, such as `int` or `Node`. */ class Type extends TType { - string toString() { result = getName() } + string toString() { result = this.getName() } string getName() { result = "???" } @@ -48,9 +48,9 @@ class Type extends TType { predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - if exists(getDeclaration()) + if exists(this.getDeclaration()) then - getDeclaration() + this.getDeclaration() .getLocation() .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) else ( @@ -72,14 +72,14 @@ class Type extends TType { private predicate getClassPredicate1( string name, int arity, PredicateOrBuiltin p1, PredicateOrBuiltin p2 ) { - getClassPredicate0(name, arity, p1, p2.getDeclaringType()) and + this.getClassPredicate0(name, arity, p1, p2.getDeclaringType()) and p2 = classPredCandidate(this, name, arity) } cached PredicateOrBuiltin getClassPredicate(string name, int arity) { result = classPredCandidate(this, name, arity) and - not getClassPredicate1(name, arity, _, result) + not this.getClassPredicate1(name, arity, _, result) } } @@ -179,7 +179,7 @@ class ClassDomainType extends Type, TClassDomain { ClassType getClassType() { result = TClass(decl) } - override Type getAnInternalSuperType() { result = getClassType().getASuperType() } + override Type getAnInternalSuperType() { result = this.getClassType().getASuperType() } } class PrimitiveType extends Type, TPrimitive { @@ -192,7 +192,7 @@ class PrimitiveType extends Type, TPrimitive { override Type getASuperType() { name = "int" and result.(PrimitiveType).getName() = "float" } override Type getAnInternalSuperType() { - result = getASuperType() + result = this.getASuperType() or result = super.getAnInternalSuperType() } @@ -232,7 +232,7 @@ class NewTypeBranchType extends Type, TNewTypeBranch { } override Type getAnInternalSuperType() { - result = getASuperType() + result = this.getASuperType() or result = super.getAnInternalSuperType() } From c31bd7a1e8329d6fc37eab04929a40e176c6527f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 08:52:32 +0200 Subject: [PATCH 21/43] fix the signature of regexpCapture and regexpFind --- ql/src/codeql_ql/ast/internal/Builtins.qll | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ql/src/codeql_ql/ast/internal/Builtins.qll b/ql/src/codeql_ql/ast/internal/Builtins.qll index 9e1d45d5242..cd012091d7d 100644 --- a/ql/src/codeql_ql/ast/internal/Builtins.qll +++ b/ql/src/codeql_ql/ast/internal/Builtins.qll @@ -35,8 +35,8 @@ predicate isBuiltinMember(string sig) { "string int.toString()", "string string.charAt(int)", "int string.indexOf(string)", "int string.indexOf(string, int, int)", "predicate string.isLowercase()", "predicate string.isUppercase()", "int string.length()", "predicate string.matches(string)", - "string string.prefix(int)", "string regexpCapture(string, int)", - "string regexpFind(string, int, int)", "predicate string.regexpMatch(string)", + "string string.prefix(int)", "string string.regexpCapture(string, int)", + "string string.regexpFind(string, int, int)", "predicate string.regexpMatch(string)", "string string.regexpReplaceAll(string, string)", "string string.replaceAll(string, string)", "string string.splitAt(string)", "string string.splitAt(string, int)", "string string.substring(int, int)", "string string.suffix(int)", "date string.toDate()", @@ -58,6 +58,18 @@ predicate isBuiltinMember(string qual, string ret, string name, string args) { ) } +module BuildinsConsistency { + predicate noBuildinParse(string sig) { + isBuiltinMember(sig) and + not exists(sig.regexpCapture("(\\w+) (\\w+)\\.(\\w+)\\(([\\w, ]*)\\)", _)) + } + + predicate noBuildinClasslessParse(string sig) { + isBuiltinClassless(sig) and + not exists(sig.regexpCapture("(\\w+) (\\w+)\\(([\\w, ]*)\\)", _)) + } +} + bindingset[args] string getArgType(string args, int i) { result = args.splitAt(",", i).trim() } From f19dd78d40abfba2607d081c2505a090d92b6b7d Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 09:09:54 +0200 Subject: [PATCH 22/43] fix `getArity` on `PredicateOrBuiltin` --- ql/src/codeql_ql/ast/Ast.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 748b95f0b45..cc876063333 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -201,7 +201,7 @@ class PredicateOrBuiltin extends TPredOrBuiltin, AstNode { Type getReturnType() { none() } - int getArity() { result = count(this.getParameterType(_)) } + int getArity() { result = count(int i | exists(this.getParameterType(i))) } predicate isPrivate() { none() } } From 1641d0fa9301a69045d4f4b2bea1776131628ca9 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 09:14:52 +0200 Subject: [PATCH 23/43] update expected output --- ql/test/printAst/printAst.expected | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ql/test/printAst/printAst.expected b/ql/test/printAst/printAst.expected index 7b296161d4d..eb4340d2adb 100644 --- a/ql/test/printAst/printAst.expected +++ b/ql/test/printAst/printAst.expected @@ -220,6 +220,8 @@ nodes | file://:0:0:0:0 | none | semmle.label | [BuiltinPredicate] none | | file://:0:0:0:0 | pow | semmle.label | [BuiltinPredicate] pow | | file://:0:0:0:0 | prefix | semmle.label | [BuiltinPredicate] prefix | +| file://:0:0:0:0 | regexpCapture | semmle.label | [BuiltinPredicate] regexpCapture | +| file://:0:0:0:0 | regexpFind | semmle.label | [BuiltinPredicate] regexpFind | | file://:0:0:0:0 | regexpMatch | semmle.label | [BuiltinPredicate] regexpMatch | | file://:0:0:0:0 | regexpReplaceAll | semmle.label | [BuiltinPredicate] regexpReplaceAll | | file://:0:0:0:0 | replaceAll | semmle.label | [BuiltinPredicate] replaceAll | From 29ebe7b13d3fd1d6aacb51535968473de2c6ab8f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 09:23:36 +0200 Subject: [PATCH 24/43] add test --- ql/test/callgraph/Foo.qll | 6 ++++++ ql/test/callgraph/callgraph.expected | 2 ++ 2 files changed, 8 insertions(+) diff --git a/ql/test/callgraph/Foo.qll b/ql/test/callgraph/Foo.qll index d9a00db69bc..3bd6484be12 100644 --- a/ql/test/callgraph/Foo.qll +++ b/ql/test/callgraph/Foo.qll @@ -31,3 +31,9 @@ module Aliases { alias0() // <- works } } + +module Buildins { + predicate replaceAll(string s) { "foo".replaceAll("foo", "bar") = s } + + predicate regexpCapture(string s) { "foo".regexpCapture("\\w", 1) = s } +} diff --git a/ql/test/callgraph/callgraph.expected b/ql/test/callgraph/callgraph.expected index 39ba4dd101a..11bbcc896d1 100644 --- a/ql/test/callgraph/callgraph.expected +++ b/ql/test/callgraph/callgraph.expected @@ -6,3 +6,5 @@ | Foo.qll:29:5:29:16 | PredicateCall | Foo.qll:26:3:26:32 | ClasslessPredicate alias2 | | Foo.qll:31:5:31:12 | PredicateCall | Foo.qll:22:3:22:32 | ClasslessPredicate myThing0 | | Foo.qll:31:5:31:12 | PredicateCall | Foo.qll:24:3:24:32 | ClasslessPredicate alias0 | +| Foo.qll:36:36:36:65 | MemberCall | file://:0:0:0:0 | replaceAll | +| Foo.qll:38:39:38:67 | MemberCall | file://:0:0:0:0 | regexpCapture | From 541dcb365f58319215db3e1db0e431cc92276d7a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 09:30:38 +0200 Subject: [PATCH 25/43] hook up consistency query --- ql/consistency-queries/BuildinsConsistency.ql | 1 + ql/src/codeql_ql/ast/internal/Builtins.qll | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 ql/consistency-queries/BuildinsConsistency.ql diff --git a/ql/consistency-queries/BuildinsConsistency.ql b/ql/consistency-queries/BuildinsConsistency.ql new file mode 100644 index 00000000000..d70e9ed077a --- /dev/null +++ b/ql/consistency-queries/BuildinsConsistency.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Builtins::BuildinsConsistency diff --git a/ql/src/codeql_ql/ast/internal/Builtins.qll b/ql/src/codeql_ql/ast/internal/Builtins.qll index cd012091d7d..78b40e56f30 100644 --- a/ql/src/codeql_ql/ast/internal/Builtins.qll +++ b/ql/src/codeql_ql/ast/internal/Builtins.qll @@ -59,12 +59,12 @@ predicate isBuiltinMember(string qual, string ret, string name, string args) { } module BuildinsConsistency { - predicate noBuildinParse(string sig) { + query predicate noBuildinParse(string sig) { isBuiltinMember(sig) and not exists(sig.regexpCapture("(\\w+) (\\w+)\\.(\\w+)\\(([\\w, ]*)\\)", _)) } - predicate noBuildinClasslessParse(string sig) { + query predicate noBuildinClasslessParse(string sig) { isBuiltinClassless(sig) and not exists(sig.regexpCapture("(\\w+) (\\w+)\\(([\\w, ]*)\\)", _)) } From e3d42a1fba904f7d6a8d37e4f2721165158c69ef Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 10:58:44 +0200 Subject: [PATCH 26/43] remove leftover test predicate --- ql/src/codeql_ql/ast/Ast.qll | 2 -- 1 file changed, 2 deletions(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index cc876063333..f650dd122c2 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -2382,8 +2382,6 @@ module YAML { // to not expose the entire `File` API on `QlPack`. private newtype TQLPack = MKQlPack(File file) { file.getBaseName() = "qlpack.yml" } - YAMLEntry test() { not result.isRoot() } - /** * A `qlpack.yml` file. */ From 816bfbe4ea5933e0eb2772c9346e74d6c5672d15 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 09:16:34 +0000 Subject: [PATCH 27/43] Upgrade the extractor generator For now, the grammar still includes dbscheme and YAML, but with this change we should be able to separate these out into their own grammars. --- Cargo.lock | 1 + create-extractor-pack.ps1 | 2 +- create-extractor-pack.sh | 2 +- extractor/src/extractor.rs | 170 +- extractor/src/main.rs | 126 +- generator/Cargo.toml | 1 + generator/src/dbscheme.rs | 18 +- generator/src/language.rs | 4 - generator/src/main.rs | 364 ++- generator/src/ql.rs | 99 +- generator/src/ql_gen.rs | 147 +- node-types/src/lib.rs | 96 +- ql/src/codeql/files/FileSystem.qll | 12 +- ql/src/codeql_ql/ast/Ast.qll | 388 +-- ql/src/codeql_ql/ast/internal/AstNodes.qll | 136 +- ql/src/codeql_ql/ast/internal/Module.qll | 6 +- ql/src/codeql_ql/ast/internal/TreeSitter.qll | 929 ++++---- ql/src/codeql_ql/ast/internal/Variable.qll | 4 +- ql/src/ql.dbscheme | 2207 +++++++++--------- 19 files changed, 2364 insertions(+), 2348 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b35c05221a..16626b5d836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,6 +329,7 @@ dependencies = [ name = "ql-generator" version = "0.1.0" dependencies = [ + "clap", "node-types", "tracing", "tracing-subscriber", diff --git a/create-extractor-pack.ps1 b/create-extractor-pack.ps1 index 775499cd7f7..9e6cb4ae295 100644 --- a/create-extractor-pack.ps1 +++ b/create-extractor-pack.ps1 @@ -1,6 +1,6 @@ cargo build --release -cargo run --release -p ql-generator +cargo run --release -p ql-generator -- --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/ codeql query format -i ql\src\codeql_ql\ast\internal\TreeSitter.qll if (Test-Path -Path extractor-pack) { diff --git a/create-extractor-pack.sh b/create-extractor-pack.sh index 09ce99751fd..5e45c41f819 100755 --- a/create-extractor-pack.sh +++ b/create-extractor-pack.sh @@ -12,7 +12,7 @@ fi cargo build --release -cargo run --release -p ql-generator +cargo run --release -p ql-generator -- --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/TreeSitter.qll codeql query format -i ql/src/codeql_ql/ast/internal/TreeSitter.qll rm -rf extractor-pack diff --git a/extractor/src/extractor.rs b/extractor/src/extractor.rs index 84d86b202c4..7c83f51e0dc 100644 --- a/extractor/src/extractor.rs +++ b/extractor/src/extractor.rs @@ -3,11 +3,13 @@ use std::borrow::Cow; use std::collections::BTreeMap as Map; use std::collections::BTreeSet as Set; use std::fmt; +use std::io::Write; use std::path::Path; + use tracing::{error, info, span, Level}; use tree_sitter::{Language, Node, Parser, Range, Tree}; -struct TrapWriter { +pub struct TrapWriter { /// The accumulated trap entries trap_output: Vec, /// A counter for generating fresh labels @@ -16,7 +18,7 @@ struct TrapWriter { global_keys: std::collections::HashMap, } -fn new_trap_writer() -> TrapWriter { +pub fn new_trap_writer() -> TrapWriter { TrapWriter { counter: 0, trap_output: Vec::new(), @@ -66,15 +68,6 @@ impl TrapWriter { vec![ Arg::Label(file_label), Arg::String(normalize_path(absolute_path)), - Arg::String(match absolute_path.file_name() { - None => "".to_owned(), - Some(file_name) => format!("{}", file_name.to_string_lossy()), - }), - Arg::String(match absolute_path.extension() { - None => "".to_owned(), - Some(ext) => format!("{}", ext.to_string_lossy()), - }), - Arg::Int(1), // 1 = from source ], ); self.populate_parent_folders(file_label, absolute_path.parent()); @@ -82,6 +75,22 @@ impl TrapWriter { file_label } + fn populate_empty_file(&mut self) -> Label { + let (file_label, fresh) = self.global_id("empty;sourcefile"); + if fresh { + self.add_tuple( + "files", + vec![Arg::Label(file_label), Arg::String("".to_string())], + ); + } + file_label + } + + pub fn populate_empty_location(&mut self) { + let file_label = self.populate_empty_file(); + self.location(file_label, 0, 0, 0, 0); + } + fn populate_parent_folders(&mut self, child_label: Label, path: Option<&Path>) { let mut path = path; let mut child_label = child_label; @@ -100,10 +109,6 @@ impl TrapWriter { vec![ Arg::Label(folder_label), Arg::String(normalize_path(folder)), - Arg::String(match folder.file_name() { - None => "".to_owned(), - Some(file_name) => format!("{}", file_name.to_string_lossy()), - }), ], ); path = folder.parent(); @@ -147,16 +152,22 @@ impl TrapWriter { fn comment(&mut self, text: String) { self.trap_output.push(TrapEntry::Comment(text)); } + + pub fn output(self, writer: &mut dyn Write) -> std::io::Result<()> { + write!(writer, "{}", Program(self.trap_output)) + } } /// Extracts the source file at `path`, which is assumed to be canonicalized. pub fn extract( language: Language, + language_prefix: &str, schema: &NodeTypeMap, + trap_writer: &mut TrapWriter, path: &Path, - source: &Vec, + source: &[u8], ranges: &[Range], -) -> std::io::Result { +) -> std::io::Result<()> { let span = span!( Level::TRACE, "extract", @@ -169,41 +180,32 @@ pub fn extract( let mut parser = Parser::new(); parser.set_language(language).unwrap(); - parser.set_included_ranges(&ranges).unwrap(); + parser.set_included_ranges(ranges).unwrap(); let tree = parser.parse(&source, None).expect("Failed to parse file"); - let mut trap_writer = new_trap_writer(); trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display())); let file_label = &trap_writer.populate_file(path); let mut visitor = Visitor { - source: &source, - trap_writer: trap_writer, + source, + trap_writer, // TODO: should we handle path strings that are not valid UTF8 better? path: format!("{}", path.display()), file_label: *file_label, - token_counter: 0, toplevel_child_counter: 0, stack: Vec::new(), + language_prefix, schema, }; traverse(&tree, &mut visitor); parser.reset(); - Ok(Program(visitor.trap_writer.trap_output)) + Ok(()) } /// Escapes a string for use in a TRAP key, by replacing special characters with /// HTML entities. fn escape_key<'a, S: Into>>(key: S) -> Cow<'a, str> { fn needs_escaping(c: char) -> bool { - match c { - '&' => true, - '{' => true, - '}' => true, - '"' => true, - '@' => true, - '#' => true, - _ => false, - } + matches!(c, '&' | '{' | '}' | '"' | '@' | '#') } let key = key.into(); @@ -286,13 +288,13 @@ struct Visitor<'a> { /// source file. file_label: Label, /// The source code as a UTF-8 byte array - source: &'a Vec, + source: &'a [u8], /// A TrapWriter to accumulate trap entries - trap_writer: TrapWriter, - /// A counter for tokens - token_counter: usize, + trap_writer: &'a mut TrapWriter, /// A counter for top-level child nodes toplevel_child_counter: usize, + /// Language prefix + language_prefix: &'a str, /// A lookup table from type name to node types schema: &'a NodeTypeMap, /// A stack for gathering information from child nodes. Whenever a node is @@ -332,7 +334,7 @@ impl Visitor<'_> { full_error_message: String, node: Node, ) { - let (start_line, start_column, end_line, end_column) = location_for(&self.source, node); + let (start_line, start_column, end_line, end_column) = location_for(self.source, node); let loc = self.trap_writer.location( self.file_label, start_line, @@ -363,7 +365,7 @@ impl Visitor<'_> { let id = self.trap_writer.fresh_id(); self.stack.push((id, 0, Vec::new())); - return true; + true } fn leave_node(&mut self, field_name: Option<&'static str>, node: Node) { @@ -371,7 +373,7 @@ impl Visitor<'_> { return; } let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack"); - let (start_line, start_column, end_line, end_column) = location_for(&self.source, node); + let (start_line, start_column, end_line, end_column) = location_for(self.source, node); let loc = self.trap_writer.location( self.file_label, start_line, @@ -400,7 +402,7 @@ impl Visitor<'_> { match &table.kind { EntryKind::Token { kind_id, .. } => { self.trap_writer.add_tuple( - "ast_node_parent", + &format!("{}_ast_node_parent", self.language_prefix), vec![ Arg::Label(id), Arg::Label(parent_id), @@ -408,17 +410,14 @@ impl Visitor<'_> { ], ); self.trap_writer.add_tuple( - "tokeninfo", + &format!("{}_tokeninfo", self.language_prefix), vec![ Arg::Label(id), Arg::Int(*kind_id), - Arg::Label(self.file_label), - Arg::Int(self.token_counter), sliced_source_arg(self.source, node), Arg::Label(loc), ], ); - self.token_counter += 1; } EntryKind::Table { fields, @@ -426,18 +425,17 @@ impl Visitor<'_> { } => { if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) { self.trap_writer.add_tuple( - "ast_node_parent", + &format!("{}_ast_node_parent", self.language_prefix), vec![ Arg::Label(id), Arg::Label(parent_id), Arg::Int(parent_index), ], ); - let mut all_args = Vec::new(); - all_args.push(Arg::Label(id)); + let mut all_args = vec![Arg::Label(id)]; all_args.extend(args); all_args.push(Arg::Label(loc)); - self.trap_writer.add_tuple(&table_name, all_args); + self.trap_writer.add_tuple(table_name, all_args); } } _ => { @@ -472,8 +470,8 @@ impl Visitor<'_> { fn complex_node( &mut self, node: &Node, - fields: &Vec, - child_nodes: &Vec, + fields: &[Field], + child_nodes: &[ChildNode], parent_id: Label, ) -> Option> { let mut map: Map<&Option, (&Field, Vec)> = Map::new(); @@ -510,22 +508,20 @@ impl Visitor<'_> { ); self.record_parse_error_for_node(error_message, full_error_message, *node); } - } else { - if child_node.field_name.is_some() || child_node.type_name.named { - let error_message = format!( - "value for unknown field: {}::{} and type {:?}", - node.kind(), - &child_node.field_name.unwrap_or("child"), - &child_node.type_name - ); - let full_error_message = format!( - "{}:{}: {}", - &self.path, - node.start_position().row + 1, - error_message - ); - self.record_parse_error_for_node(error_message, full_error_message, *node); - } + } else if child_node.field_name.is_some() || child_node.type_name.named { + let error_message = format!( + "value for unknown field: {}::{} and type {:?}", + node.kind(), + &child_node.field_name.unwrap_or("child"), + &child_node.type_name + ); + let full_error_message = format!( + "{}:{}: {}", + &self.path, + node.start_position().row + 1, + error_message + ); + self.record_parse_error_for_node(error_message, full_error_message, *node); } } let mut args = Vec::new(); @@ -573,13 +569,12 @@ impl Visitor<'_> { ); break; } - let mut args = Vec::new(); - args.push(Arg::Label(parent_id)); + let mut args = vec![Arg::Label(parent_id)]; if *has_index { args.push(Arg::Int(index)) } args.push(child_value.clone()); - self.trap_writer.add_tuple(&table_name, args); + self.trap_writer.add_tuple(table_name, args); } } } @@ -597,13 +592,10 @@ impl Visitor<'_> { if tp == single_type { return true; } - match &self.schema.get(single_type).unwrap().kind { - EntryKind::Union { members } => { - if self.type_matches_set(tp, members) { - return true; - } + if let EntryKind::Union { members } = &self.schema.get(single_type).unwrap().kind { + if self.type_matches_set(tp, members) { + return true; } - _ => {} } } node_types::FieldTypeInfo::Multiple { types, .. } => { @@ -633,7 +625,7 @@ impl Visitor<'_> { } // Emit a slice of a source file as an Arg. -fn sliced_source_arg(source: &Vec, n: Node) -> Arg { +fn sliced_source_arg(source: &[u8], n: Node) -> Arg { let range = n.byte_range(); Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned()) } @@ -641,7 +633,7 @@ fn sliced_source_arg(source: &Vec, n: Node) -> Arg { // Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated. // The first is the location and label definition, and the second is the // 'Located' entry. -fn location_for<'a>(source: &Vec, n: Node) -> (usize, usize, usize, usize) { +fn location_for(source: &[u8], n: Node) -> (usize, usize, usize, usize) { // Tree-sitter row, column values are 0-based while CodeQL starts // counting at 1. In addition Tree-sitter's row and column for the // end position are exclusive while CodeQL's end positions are inclusive. @@ -720,9 +712,9 @@ impl fmt::Display for Program { } enum TrapEntry { - /// Maps the label to a fresh id, e.g. `#123 = *`. + /// Maps the label to a fresh id, e.g. `#123=*`. FreshId(Label), - /// Maps the label to a key, e.g. `#7 = @"foo"`. + /// Maps the label to a key, e.g. `#7=@"foo"`. MapLabelToKey(Label, String), /// foo_bar(arg*) GenericTuple(String, Vec), @@ -731,15 +723,15 @@ enum TrapEntry { impl fmt::Display for TrapEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - TrapEntry::FreshId(label) => write!(f, "{} = *", label), + TrapEntry::FreshId(label) => write!(f, "{}=*", label), TrapEntry::MapLabelToKey(label, key) => { - write!(f, "{} = @\"{}\"", label, key.replace("\"", "\"\"")) + write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\"")) } TrapEntry::GenericTuple(name, args) => { write!(f, "{}(", name)?; for (index, arg) in args.iter().enumerate() { if index > 0 { - write!(f, ", ")?; + write!(f, ",")?; } write!(f, "{}", arg)?; } @@ -756,7 +748,7 @@ struct Label(u32); impl fmt::Display for Label { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "#{}", self.0) + write!(f, "#{:x}", self.0) } } @@ -799,18 +791,18 @@ impl fmt::Display for Arg { /// the string is sliced at the provided limit. If there is a multi-byte character /// at the limit then the returned slice will be slightly shorter than the limit to /// avoid splitting that multi-byte character. -fn limit_string(string: &String, max_size: usize) -> &str { +fn limit_string(string: &str, max_size: usize) -> &str { if string.len() <= max_size { return string; } - let p = string.as_ptr(); + let p = string.as_bytes(); let mut index = max_size; // We want to clip the string at [max_size]; however, the character at that position // may span several bytes. We need to find the first byte of the character. In UTF-8 // encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte. // Therefore we decrement the index as long as there are bytes matching this pattern. // This ensures we cut the string at the border between one character and another. - while index > 0 && unsafe { (*p.offset(index as isize) & 0b11000000) == 0b10000000 } { + while index > 0 && (p[index] & 0b11000000) == 0b10000000 { index -= 1; } &string[0..index] @@ -829,9 +821,9 @@ fn escape_key_test() { assert_eq!("foo{}", escape_key("foo{}")); assert_eq!("{}", escape_key("{}")); assert_eq!("", escape_key("")); - assert_eq!("/path/to/foo.ql", escape_key("/path/to/foo.ql")); + assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb")); assert_eq!( - "/path/to/foo&{}"@#.ql", - escape_key("/path/to/foo&{}\"@#.ql") + "/path/to/foo&{}"@#.rb", + escape_key("/path/to/foo&{}\"@#.rb") ); } diff --git a/extractor/src/main.rs b/extractor/src/main.rs index b74126ffd0f..0fcf9607702 100644 --- a/extractor/src/main.rs +++ b/extractor/src/main.rs @@ -2,13 +2,11 @@ mod extractor; extern crate num_cpus; -use clap; use flate2::write::GzEncoder; use rayon::prelude::*; use std::fs; -use std::io::{BufRead, BufWriter, Write}; +use std::io::{BufRead, BufWriter}; use std::path::{Path, PathBuf}; -use tree_sitter::{Language, Parser, Range}; enum TrapCompression { None, @@ -40,8 +38,8 @@ impl TrapCompression { fn extension(&self) -> &str { match self { - TrapCompression::None => ".trap", - TrapCompression::Gzip => ".trap.gz", + TrapCompression::None => "trap", + TrapCompression::Gzip => "trap.gz", } } } @@ -54,28 +52,24 @@ impl TrapCompression { * "If the number is positive, it indicates the number of threads that should * be used. If the number is negative or zero, it should be added to the number * of cores available on the machine to determine how many threads to use - * (minimum of 1). If unspecified, should be considered as set to 1." + * (minimum of 1). If unspecified, should be considered as set to -1." */ fn num_codeql_threads() -> usize { - match std::env::var("CODEQL_THREADS") { - // Use 1 thread if the environment variable isn't set. - Err(_) => 1, + let threads_str = std::env::var("CODEQL_THREADS").unwrap_or_else(|_| "-1".to_owned()); + match threads_str.parse::() { + Ok(num) if num <= 0 => { + let reduction = -num as usize; + std::cmp::max(1, num_cpus::get() - reduction) + } + Ok(num) => num as usize, - Ok(num) => match num.parse::() { - Ok(num) if num <= 0 => { - let reduction = -num as usize; - num_cpus::get() - reduction - } - Ok(num) => num as usize, - - Err(_) => { - tracing::error!( - "Unable to parse CODEQL_THREADS value '{}'; defaulting to 1 thread.", - &num - ); - 1 - } - }, + Err(_) => { + tracing::error!( + "Unable to parse CODEQL_THREADS value '{}'; defaulting to 1 thread.", + &threads_str + ); + 1 + } } } @@ -127,29 +121,55 @@ fn main() -> std::io::Result<()> { let file_list = fs::File::open(file_list)?; let language = tree_sitter_ql::language(); - let schema = node_types::read_node_types_str(tree_sitter_ql::NODE_TYPES)?; + let schema = node_types::read_node_types_str("ql", tree_sitter_ql::NODE_TYPES)?; let lines: std::io::Result> = std::io::BufReader::new(file_list).lines().collect(); let lines = lines?; - lines.par_iter().try_for_each(|line| { - let path = PathBuf::from(line).canonicalize()?; - let trap_file = path_for(&trap_dir, &path, trap_compression.extension()); - let src_archive_file = path_for(&src_archive_dir, &path, ""); - let mut source = std::fs::read(&path)?; - let code_ranges = vec![]; - let trap = extractor::extract(language, &schema, &path, &source, &code_ranges)?; - std::fs::create_dir_all(&src_archive_file.parent().unwrap())?; - std::fs::copy(&path, &src_archive_file)?; - std::fs::create_dir_all(&trap_file.parent().unwrap())?; - let trap_file = std::fs::File::create(&trap_file)?; - let mut trap_file = BufWriter::new(trap_file); - match trap_compression { - TrapCompression::None => write!(trap_file, "{}", trap), - TrapCompression::Gzip => { - let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast()); - write!(compressed_writer, "{}", trap) - } + lines + .par_iter() + .try_for_each(|line| { + let path = PathBuf::from(line).canonicalize()?; + let src_archive_file = path_for(&src_archive_dir, &path, ""); + let source = std::fs::read(&path)?; + let code_ranges = vec![]; + let mut trap_writer = extractor::new_trap_writer(); + extractor::extract( + language, + "ql", + &schema, + &mut trap_writer, + &path, + &source, + &code_ranges, + )?; + std::fs::create_dir_all(&src_archive_file.parent().unwrap())?; + std::fs::copy(&path, &src_archive_file)?; + write_trap(&trap_dir, path, trap_writer, &trap_compression) + }) + .expect("failed to extract files"); + + let path = PathBuf::from("extras"); + let mut trap_writer = extractor::new_trap_writer(); + trap_writer.populate_empty_location(); + write_trap(&trap_dir, path, trap_writer, &trap_compression) +} + +fn write_trap( + trap_dir: &Path, + path: PathBuf, + trap_writer: extractor::TrapWriter, + trap_compression: &TrapCompression, +) -> std::io::Result<()> { + let trap_file = path_for(trap_dir, &path, trap_compression.extension()); + std::fs::create_dir_all(&trap_file.parent().unwrap())?; + let trap_file = std::fs::File::create(&trap_file)?; + let mut trap_file = BufWriter::new(trap_file); + match trap_compression { + TrapCompression::None => trap_writer.output(&mut trap_file), + TrapCompression::Gzip => { + let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast()); + trap_writer.output(&mut compressed_writer) } - }) + } } fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf { @@ -184,12 +204,18 @@ fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf { } } } - if let Some(x) = result.extension() { - let mut new_ext = x.to_os_string(); - new_ext.push(ext); - result.set_extension(new_ext); - } else { - result.set_extension(ext); + if !ext.is_empty() { + match result.extension() { + Some(x) => { + let mut new_ext = x.to_os_string(); + new_ext.push("."); + new_ext.push(ext); + result.set_extension(new_ext); + } + None => { + result.set_extension(ext); + } + } } result } diff --git a/generator/Cargo.toml b/generator/Cargo.toml index 93117ef1c3e..38074ab4e91 100644 --- a/generator/Cargo.toml +++ b/generator/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "2.33" node-types = { path = "../node-types" } tracing = "0.1" tracing-subscriber = { version = "0.2", features = ["env-filter"] } diff --git a/generator/src/dbscheme.rs b/generator/src/dbscheme.rs index 84cc329d065..335eee1950c 100644 --- a/generator/src/dbscheme.rs +++ b/generator/src/dbscheme.rs @@ -68,10 +68,10 @@ impl<'a> fmt::Display for Table<'a> { } write!(f, "{}", key)?; } - write!(f, "]\n")?; + writeln!(f, "]")?; } - write!(f, "{}(\n", self.name)?; + writeln!(f, "{}(", self.name)?; for (column_index, column) in self.columns.iter().enumerate() { write!(f, " ")?; if column.unique { @@ -92,7 +92,7 @@ impl<'a> fmt::Display for Table<'a> { if column_index + 1 != self.columns.len() { write!(f, ",")?; } - write!(f, "\n")?; + writeln!(f)?; } write!(f, ");")?; @@ -117,17 +117,7 @@ impl<'a> fmt::Display for Union<'a> { } /// Generates the dbscheme by writing the given dbscheme `entries` to the `file`. -pub fn write<'a>( - language_name: &str, - file: &mut dyn std::io::Write, - entries: &'a [Entry], -) -> std::io::Result<()> { - write!(file, "// CodeQL database schema for {}\n", language_name)?; - write!( - file, - "// Automatically generated from the tree-sitter grammar; do not edit\n\n" - )?; - +pub fn write<'a>(file: &mut dyn std::io::Write, entries: &'a [Entry]) -> std::io::Result<()> { for entry in entries { match entry { Entry::Case(case) => write!(file, "{}\n\n", case)?, diff --git a/generator/src/language.rs b/generator/src/language.rs index 4e2f985a21c..f0b0ed1790f 100644 --- a/generator/src/language.rs +++ b/generator/src/language.rs @@ -1,8 +1,4 @@ -use std::path::PathBuf; - pub struct Language { pub name: String, pub node_types: &'static str, - pub dbscheme_path: PathBuf, - pub ql_library_path: PathBuf, } diff --git a/generator/src/main.rs b/generator/src/main.rs index 13922c4b84c..04e7cd61d2c 100644 --- a/generator/src/main.rs +++ b/generator/src/main.rs @@ -8,8 +8,8 @@ use std::collections::BTreeMap as Map; use std::collections::BTreeSet as Set; use std::fs::File; use std::io::LineWriter; +use std::io::Write; use std::path::PathBuf; -use tracing::{error, info}; /// Given the name of the parent node, and its field information, returns a pair, /// the first of which is the field's type. The second is an optional dbscheme @@ -32,7 +32,7 @@ fn make_field_type<'a>( .map(|t| nodes.get(t).unwrap().dbscheme_name.as_str()) .collect(); ( - ql::Type::AtType(&dbscheme_union), + ql::Type::At(dbscheme_union), Some(dbscheme::Entry::Union(dbscheme::Union { name: dbscheme_union, members, @@ -40,14 +40,14 @@ fn make_field_type<'a>( ) } node_types::FieldTypeInfo::Single(t) => { - let dbscheme_name = &nodes.get(&t).unwrap().dbscheme_name; - (ql::Type::AtType(dbscheme_name), None) + let dbscheme_name = &nodes.get(t).unwrap().dbscheme_name; + (ql::Type::At(dbscheme_name), None) } node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => { // The field will be an `int` in the db, and we add a case split to // create other db types for each integer value. let mut branches: Vec<(usize, &'a str)> = Vec::new(); - for (_, (value, name)) in int_mapping { + for (value, name) in int_mapping.values() { branches.push((*value, name)); } let case = dbscheme::Entry::Case(dbscheme::Case { @@ -73,12 +73,12 @@ fn add_field_for_table_storage<'a>( let parent_name = &nodes.get(&field.parent).unwrap().dbscheme_name; // This field can appear zero or multiple times, so put // it in an auxiliary table. - let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes); + let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes); let parent_column = dbscheme::Column { unique: !has_index, db_type: dbscheme::DbColumnType::Int, - name: &parent_name, - ql_type: ql::Type::AtType(&parent_name), + name: parent_name, + ql_type: ql::Type::At(parent_name), ql_type_is_ref: true, }; let index_column = dbscheme::Column { @@ -96,7 +96,7 @@ fn add_field_for_table_storage<'a>( ql_type_is_ref: true, }; let field_table = dbscheme::Table { - name: &table_name, + name: table_name, columns: if has_index { vec![parent_column, index_column, field_column] } else { @@ -105,7 +105,7 @@ fn add_field_for_table_storage<'a>( // In addition to the field being unique, the combination of // parent+index is unique, so add a keyset for them. keysets: if has_index { - Some(vec![&parent_name, "index"]) + Some(vec![parent_name, "index"]) } else { None }, @@ -121,7 +121,7 @@ fn add_field_for_column_storage<'a>( ) -> (dbscheme::Column<'a>, Option>) { // This field must appear exactly once, so we add it as // a column to the main table for the node type. - let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes); + let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes); ( dbscheme::Column { unique: false, @@ -135,18 +135,16 @@ fn add_field_for_column_storage<'a>( } /// Converts the given tree-sitter node types into CodeQL dbscheme entries. -fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec> { - let mut entries: Vec = vec![ - create_location_union(), - create_locations_default_table(), - create_sourceline_union(), - create_numlines_table(), - create_files_table(), - create_folders_table(), - create_container_union(), - create_containerparent_table(), - create_source_location_prefix_table(), - ]; +/// Returns a tuple containing: +/// +/// 1. A vector of dbscheme entries. +/// 2. A set of names of the members of the `_ast_node` union. +/// 3. A map where the keys are the dbscheme names for token kinds, and the +/// values are their integer representations. +fn convert_nodes( + nodes: &node_types::NodeTypeMap, +) -> (Vec, Set<&str>, Map<&str, usize>) { + let mut entries: Vec = Vec::new(); let mut ast_node_members: Set<&str> = Set::new(); let token_kinds: Map<&str, usize> = nodes .iter() @@ -157,8 +155,7 @@ fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec None, }) .collect(); - ast_node_members.insert("token"); - for (_, node) in nodes { + for node in nodes.values() { match &node.kind { node_types::EntryKind::Union { members: n_members } => { // It's a tree-sitter supertype node, for which we create a union @@ -175,12 +172,12 @@ fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec { // It's a product type, defined by a table. let mut main_table = dbscheme::Table { - name: &name, + name, columns: vec![dbscheme::Column { db_type: dbscheme::DbColumnType::Int, name: "id", unique: true, - ql_type: ql::Type::AtType(&node.dbscheme_name), + ql_type: ql::Type::At(&node.dbscheme_name), ql_type_is_ref: false, }], keysets: None, @@ -240,7 +237,7 @@ fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec(nodes: &'a node_types::NodeTypeMap) -> Vec() -> dbscheme::Table<'a> { + +/// Creates a dbscheme table entry representing the parent relation for AST nodes. +/// +/// # Arguments +/// - `name` - the name of both the table to create and the node parent type. +/// - `ast_node_name` - the name of the node child type. +fn create_ast_node_parent_table<'a>(name: &'a str, ast_node_name: &'a str) -> dbscheme::Table<'a> { dbscheme::Table { - name: "ast_node_parent", + name, columns: vec![ dbscheme::Column { db_type: dbscheme::DbColumnType::Int, name: "child", unique: false, - ql_type: ql::Type::AtType("ast_node"), + ql_type: ql::Type::At(ast_node_name), ql_type_is_ref: true, }, dbscheme::Column { db_type: dbscheme::DbColumnType::Int, name: "parent", unique: false, - ql_type: ql::Type::AtType("ast_node_parent"), + ql_type: ql::Type::At(name), ql_type_is_ref: true, }, dbscheme::Column { @@ -306,18 +285,16 @@ fn create_ast_node_parent_table<'a>() -> dbscheme::Table<'a> { } } -fn create_tokeninfo<'a>( - token_kinds: Map<&'a str, usize>, -) -> (dbscheme::Case<'a>, dbscheme::Table<'a>) { - let table = dbscheme::Table { - name: "tokeninfo", +fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a> { + dbscheme::Table { + name, keysets: None, columns: vec![ dbscheme::Column { db_type: dbscheme::DbColumnType::Int, name: "id", unique: true, - ql_type: ql::Type::AtType("token"), + ql_type: ql::Type::At(type_name), ql_type_is_ref: false, }, dbscheme::Column { @@ -327,20 +304,6 @@ fn create_tokeninfo<'a>( ql_type: ql::Type::Int, ql_type_is_ref: true, }, - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "file", - ql_type: ql::Type::AtType("file"), - ql_type_is_ref: true, - }, - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "idx", - ql_type: ql::Type::Int, - ql_type_is_ref: true, - }, dbscheme::Column { unique: false, db_type: dbscheme::DbColumnType::String, @@ -352,35 +315,23 @@ fn create_tokeninfo<'a>( unique: false, db_type: dbscheme::DbColumnType::Int, name: "loc", - ql_type: ql::Type::AtType("location"), + ql_type: ql::Type::At("location"), ql_type_is_ref: true, }, ], - }; + } +} + +fn create_token_case<'a>(name: &'a str, token_kinds: Map<&'a str, usize>) -> dbscheme::Case<'a> { let branches: Vec<(usize, &str)> = token_kinds .iter() .map(|(&name, kind_id)| (*kind_id, name)) .collect(); - let case = dbscheme::Case { - name: "token", + dbscheme::Case { + name, column: "kind", - branches: branches, - }; - (case, table) -} - -fn write_dbscheme(language: &Language, entries: &[dbscheme::Entry]) -> std::io::Result<()> { - info!( - "Writing database schema for {} to '{}'", - &language.name, - match language.dbscheme_path.to_str() { - None => "", - Some(p) => p, - } - ); - let file = File::create(&language.dbscheme_path)?; - let mut file = LineWriter::new(file); - dbscheme::write(&language.name, &mut file, &entries) + branches, + } } fn create_location_union<'a>() -> dbscheme::Entry<'a> { @@ -399,7 +350,7 @@ fn create_files_table<'a>() -> dbscheme::Entry<'a> { unique: true, db_type: dbscheme::DbColumnType::Int, name: "id", - ql_type: ql::Type::AtType("file"), + ql_type: ql::Type::At("file"), ql_type_is_ref: false, }, dbscheme::Column { @@ -409,27 +360,6 @@ fn create_files_table<'a>() -> dbscheme::Entry<'a> { ql_type: ql::Type::String, ql_type_is_ref: true, }, - dbscheme::Column { - db_type: dbscheme::DbColumnType::String, - name: "simple", - unique: false, - ql_type: ql::Type::String, - ql_type_is_ref: true, - }, - dbscheme::Column { - db_type: dbscheme::DbColumnType::String, - name: "ext", - unique: false, - ql_type: ql::Type::String, - ql_type_is_ref: true, - }, - dbscheme::Column { - db_type: dbscheme::DbColumnType::Int, - name: "fromSource", - unique: false, - ql_type: ql::Type::Int, - ql_type_is_ref: true, - }, ], }) } @@ -442,7 +372,7 @@ fn create_folders_table<'a>() -> dbscheme::Entry<'a> { unique: true, db_type: dbscheme::DbColumnType::Int, name: "id", - ql_type: ql::Type::AtType("folder"), + ql_type: ql::Type::At("folder"), ql_type_is_ref: false, }, dbscheme::Column { @@ -452,13 +382,6 @@ fn create_folders_table<'a>() -> dbscheme::Entry<'a> { ql_type: ql::Type::String, ql_type_is_ref: true, }, - dbscheme::Column { - db_type: dbscheme::DbColumnType::String, - name: "simple", - unique: false, - ql_type: ql::Type::String, - ql_type_is_ref: true, - }, ], }) } @@ -472,14 +395,14 @@ fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> { unique: true, db_type: dbscheme::DbColumnType::Int, name: "id", - ql_type: ql::Type::AtType("location_default"), + ql_type: ql::Type::At("location_default"), ql_type_is_ref: false, }, dbscheme::Column { unique: false, db_type: dbscheme::DbColumnType::Int, name: "file", - ql_type: ql::Type::AtType("file"), + ql_type: ql::Type::At("file"), ql_type_is_ref: true, }, dbscheme::Column { @@ -514,50 +437,6 @@ fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> { }) } -fn create_sourceline_union<'a>() -> dbscheme::Entry<'a> { - dbscheme::Entry::Union(dbscheme::Union { - name: "sourceline", - members: vec!["file"].into_iter().collect(), - }) -} - -fn create_numlines_table<'a>() -> dbscheme::Entry<'a> { - dbscheme::Entry::Table(dbscheme::Table { - name: "numlines", - columns: vec![ - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "element_id", - ql_type: ql::Type::AtType("sourceline"), - ql_type_is_ref: true, - }, - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "num_lines", - ql_type: ql::Type::Int, - ql_type_is_ref: true, - }, - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "num_code", - ql_type: ql::Type::Int, - ql_type_is_ref: true, - }, - dbscheme::Column { - unique: false, - db_type: dbscheme::DbColumnType::Int, - name: "num_comment", - ql_type: ql::Type::Int, - ql_type_is_ref: true, - }, - ], - keysets: None, - }) -} - fn create_container_union<'a>() -> dbscheme::Entry<'a> { dbscheme::Entry::Union(dbscheme::Union { name: "container", @@ -573,14 +452,14 @@ fn create_containerparent_table<'a>() -> dbscheme::Entry<'a> { unique: false, db_type: dbscheme::DbColumnType::Int, name: "parent", - ql_type: ql::Type::AtType("container"), + ql_type: ql::Type::At("container"), ql_type_is_ref: true, }, dbscheme::Column { unique: true, db_type: dbscheme::DbColumnType::Int, name: "child", - ql_type: ql::Type::AtType("container"), + ql_type: ql::Type::At("container"), ql_type_is_ref: true, }, ], @@ -611,7 +490,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) { unique: true, db_type: dbscheme::DbColumnType::Int, name: "id", - ql_type: ql::Type::AtType("diagnostic"), + ql_type: ql::Type::At("diagnostic"), ql_type_is_ref: false, }, dbscheme::Column { @@ -646,7 +525,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) { unique: false, db_type: dbscheme::DbColumnType::Int, name: "location", - ql_type: ql::Type::AtType("location_default"), + ql_type: ql::Type::At("location_default"), ql_type_is_ref: true, }, ], @@ -665,7 +544,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) { (case, table) } -fn main() { +fn main() -> std::io::Result<()> { tracing_subscriber::fmt() .with_target(false) .without_time() @@ -673,33 +552,114 @@ fn main() { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); - // TODO: figure out proper dbscheme output path and/or take it from the - // command line. - let ql = Language { + let matches = clap::App::new("QL dbscheme generator") + .version("1.0") + .author("GitHub") + .about("CodeQL QL dbscheme generator") + .args_from_usage( + "--dbscheme= 'Path of the generated dbscheme file' + --library= 'Path of the generated QLL file'", + ) + .get_matches(); + let dbscheme_path = matches.value_of("dbscheme").expect("missing --dbscheme"); + let dbscheme_path = PathBuf::from(dbscheme_path); + + let ql_library_path = matches.value_of("library").expect("missing --library"); + let ql_library_path = PathBuf::from(ql_library_path); + + let languages = vec![Language { name: "QL".to_owned(), node_types: tree_sitter_ql::NODE_TYPES, - dbscheme_path: PathBuf::from("ql/src/ql.dbscheme"), - ql_library_path: PathBuf::from("ql/src/codeql_ql/ast/internal/TreeSitter.qll"), - }; - match node_types::read_node_types_str(&ql.node_types) { - Err(e) => { - error!("Failed to read node-types JSON for {}: {}", ql.name, e); - std::process::exit(1); - } - Ok(nodes) => { - let dbscheme_entries = convert_nodes(&nodes); + }]; + let mut dbscheme_writer = LineWriter::new(File::create(dbscheme_path)?); + write!( + dbscheme_writer, + "// CodeQL database schema for {}\n\ + // Automatically generated from the tree-sitter grammar; do not edit\n\n", + languages[0].name + )?; + let (diagnostics_case, diagnostics_table) = create_diagnostics(); + dbscheme::write( + &mut dbscheme_writer, + &[ + create_location_union(), + create_locations_default_table(), + create_files_table(), + create_folders_table(), + create_container_union(), + create_containerparent_table(), + create_source_location_prefix_table(), + dbscheme::Entry::Table(diagnostics_table), + dbscheme::Entry::Case(diagnostics_case), + ], + )?; - if let Err(e) = write_dbscheme(&ql, &dbscheme_entries) { - error!("Failed to write dbscheme: {}", e); - std::process::exit(2); - } + let mut ql_writer = LineWriter::new(File::create(ql_library_path)?); + write!( + ql_writer, + "/*\n\ + * CodeQL library for {} + * Automatically generated from the tree-sitter grammar; do not edit\n\ + */\n\n", + languages[0].name + )?; + ql::write( + &mut ql_writer, + &[ + ql::TopLevel::Import("codeql.files.FileSystem"), + ql::TopLevel::Import("codeql.Locations"), + ], + )?; - let classes = ql_gen::convert_nodes(&nodes); + for language in languages { + let prefix = node_types::to_snake_case(&language.name); + let ast_node_name = format!("{}_ast_node", &prefix); + let ast_node_parent_name = format!("{}_ast_node_parent", &prefix); + let token_name = format!("{}_token", &prefix); + let tokeninfo_name = format!("{}_tokeninfo", &prefix); + let reserved_word_name = format!("{}_reserved_word", &prefix); + let nodes = node_types::read_node_types_str(&prefix, language.node_types)?; + let (dbscheme_entries, mut ast_node_members, token_kinds) = convert_nodes(&nodes); + ast_node_members.insert(&token_name); + dbscheme::write(&mut dbscheme_writer, &dbscheme_entries)?; + let token_case = create_token_case(&token_name, token_kinds); + dbscheme::write( + &mut dbscheme_writer, + &[ + dbscheme::Entry::Table(create_tokeninfo(&tokeninfo_name, &token_name)), + dbscheme::Entry::Case(token_case), + dbscheme::Entry::Union(dbscheme::Union { + name: &ast_node_name, + members: ast_node_members, + }), + dbscheme::Entry::Union(dbscheme::Union { + name: &ast_node_parent_name, + members: [&ast_node_name, "file"].iter().cloned().collect(), + }), + dbscheme::Entry::Table(create_ast_node_parent_table( + &ast_node_parent_name, + &ast_node_name, + )), + ], + )?; - if let Err(e) = ql_gen::write(&ql, &classes) { - println!("Failed to write QL library: {}", e); - std::process::exit(3); - } - } + let mut body = vec![ + ql::TopLevel::Class(ql_gen::create_ast_node_class( + &ast_node_name, + &ast_node_parent_name, + )), + ql::TopLevel::Class(ql_gen::create_token_class(&token_name, &tokeninfo_name)), + ql::TopLevel::Class(ql_gen::create_reserved_word_class(&reserved_word_name)), + ]; + body.append(&mut ql_gen::convert_nodes(&nodes)); + ql::write( + &mut ql_writer, + &[ql::TopLevel::Module(ql::Module { + qldoc: None, + name: &language.name, + body, + })], + )?; } + Ok(()) } diff --git a/generator/src/ql.rs b/generator/src/ql.rs index b1b2f7529d2..c51903529eb 100644 --- a/generator/src/ql.rs +++ b/generator/src/ql.rs @@ -1,9 +1,11 @@ use std::collections::BTreeSet; use std::fmt; +#[derive(Clone, Eq, PartialEq, Hash)] pub enum TopLevel<'a> { Class(Class<'a>), Import(&'a str), + Module(Module<'a>), } impl<'a> fmt::Display for TopLevel<'a> { @@ -11,6 +13,7 @@ impl<'a> fmt::Display for TopLevel<'a> { match self { TopLevel::Import(x) => write!(f, "private import {}", x), TopLevel::Class(cls) => write!(f, "{}", cls), + TopLevel::Module(m) => write!(f, "{}", m), } } } @@ -40,15 +43,15 @@ impl<'a> fmt::Display for Class<'a> { } write!(f, "{}", supertype)?; } - write!(f, " {{ \n")?; + writeln!(f, " {{ ")?; if let Some(charpred) = &self.characteristic_predicate { - write!( + writeln!( f, - " {}\n", + " {}", Predicate { qldoc: None, - name: self.name.clone(), + name: self.name, overridden: false, return_type: None, formal_parameters: vec![], @@ -58,7 +61,7 @@ impl<'a> fmt::Display for Class<'a> { } for predicate in &self.predicates { - write!(f, " {}\n", predicate)?; + writeln!(f, " {}", predicate)?; } write!(f, "}}")?; @@ -67,6 +70,26 @@ impl<'a> fmt::Display for Class<'a> { } } +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Module<'a> { + pub qldoc: Option, + pub name: &'a str, + pub body: Vec>, +} + +impl<'a> fmt::Display for Module<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(qldoc) = &self.qldoc { + write!(f, "/** {} */", qldoc)?; + } + writeln!(f, "module {} {{ ", self.name)?; + for decl in &self.body { + writeln!(f, " {}", decl)?; + } + write!(f, "}}")?; + Ok(()) + } +} // The QL type of a column. #[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Type<'a> { @@ -77,7 +100,7 @@ pub enum Type<'a> { String, /// A database type that will need to be referred to with an `@` prefix. - AtType(&'a str), + At(&'a str), /// A user-defined type. Normal(&'a str), @@ -89,7 +112,7 @@ impl<'a> fmt::Display for Type<'a> { Type::Int => write!(f, "int"), Type::String => write!(f, "string"), Type::Normal(name) => write!(f, "{}", name), - Type::AtType(name) => write!(f, "@{}", name), + Type::At(name) => write!(f, "@{}", name), } } } @@ -104,12 +127,13 @@ pub enum Expression<'a> { Or(Vec>), Equals(Box>, Box>), Dot(Box>, &'a str, Vec>), - Aggregate( - &'a str, - Vec>, - Box>, - Box>, - ), + Aggregate { + name: &'a str, + vars: Vec>, + range: Option>>, + expr: Box>, + second_expr: Option>>, + }, } impl<'a> fmt::Display for Expression<'a> { @@ -165,15 +189,31 @@ impl<'a> fmt::Display for Expression<'a> { } write!(f, ")") } - Expression::Aggregate(n, vars, range, term) => { - write!(f, "{}(", n)?; - for (index, var) in vars.iter().enumerate() { - if index > 0 { - write!(f, ", ")?; + Expression::Aggregate { + name, + vars, + range, + expr, + second_expr, + } => { + write!(f, "{}(", name)?; + if !vars.is_empty() { + for (index, var) in vars.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", var)?; } - write!(f, "{}", var)?; + write!(f, " | ")?; } - write!(f, " | {} | {})", range, term) + if let Some(range) = range { + write!(f, "{} | ", range)?; + } + write!(f, "{}", expr)?; + if let Some(second_expr) = second_expr { + write!(f, ", {}", second_expr)?; + } + write!(f, ")") } } } @@ -226,25 +266,10 @@ impl<'a> fmt::Display for FormalParameter<'a> { } } -/// Generates a QL library by writing the given `classes` to the `file`. -pub fn write<'a>( - language_name: &str, - file: &mut dyn std::io::Write, - elements: &'a [TopLevel], -) -> std::io::Result<()> { - write!(file, "/*\n")?; - write!(file, " * CodeQL library for {}\n", language_name)?; - write!( - file, - " * Automatically generated from the tree-sitter grammar; do not edit\n" - )?; - write!(file, " */\n\n")?; - write!(file, "module Generated {{\n")?; - +/// Generates a QL library by writing the given `elements` to the `file`. +pub fn write<'a>(file: &mut dyn std::io::Write, elements: &'a [TopLevel]) -> std::io::Result<()> { for element in elements { write!(file, "{}\n\n", &element)?; } - - write!(file, "}}")?; Ok(()) } diff --git a/generator/src/ql_gen.rs b/generator/src/ql_gen.rs index 4fbe3449e63..a9a276fd9f6 100644 --- a/generator/src/ql_gen.rs +++ b/generator/src/ql_gen.rs @@ -1,32 +1,9 @@ -use crate::language::Language; use crate::ql; use std::collections::BTreeSet; -use std::fs::File; -use std::io::LineWriter; - -/// Writes the QL AST library for the given library. -/// -/// # Arguments -/// -/// `language` - the language for which we're generating a library -/// `classes` - the list of classes to write. -pub fn write(language: &Language, classes: &[ql::TopLevel]) -> std::io::Result<()> { - println!( - "Writing QL library for {} to '{}'", - &language.name, - match language.ql_library_path.to_str() { - None => "", - Some(p) => p, - } - ); - let file = File::create(&language.ql_library_path)?; - let mut file = LineWriter::new(file); - ql::write(&language.name, &mut file, &classes) -} /// Creates the hard-coded `AstNode` class that acts as a supertype of all /// classes we generate. -fn create_ast_node_class<'a>() -> ql::Class<'a> { +pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) -> ql::Class<'a> { // Default implementation of `toString` calls `this.getAPrimaryQlClass()` let to_string = ql::Predicate { qldoc: Some(String::from( @@ -64,7 +41,7 @@ fn create_ast_node_class<'a>() -> ql::Class<'a> { return_type: Some(ql::Type::Normal("AstNode")), formal_parameters: vec![], body: ql::Expression::Pred( - "ast_node_parent", + ast_node_parent, vec![ ql::Expression::Var("this"), ql::Expression::Var("result"), @@ -81,7 +58,7 @@ fn create_ast_node_class<'a>() -> ql::Class<'a> { return_type: Some(ql::Type::Int), formal_parameters: vec![], body: ql::Expression::Pred( - "ast_node_parent", + ast_node_parent, vec![ ql::Expression::Var("this"), ql::Expression::Var("_"), @@ -102,11 +79,36 @@ fn create_ast_node_class<'a>() -> ql::Class<'a> { Box::new(ql::Expression::String("???")), ), }; + let get_primary_ql_classes = ql::Predicate { + qldoc: Some( + "Gets a comma-separated list of the names of the primary CodeQL \ + classes to which this element belongs." + .to_owned(), + ), + name: "getPrimaryQlClasses", + overridden: false, + return_type: Some(ql::Type::String), + formal_parameters: vec![], + body: ql::Expression::Equals( + Box::new(ql::Expression::Var("result")), + Box::new(ql::Expression::Aggregate { + name: "concat", + vars: vec![], + range: None, + expr: Box::new(ql::Expression::Dot( + Box::new(ql::Expression::Var("this")), + "getAPrimaryQlClass", + vec![], + )), + second_expr: Some(Box::new(ql::Expression::String(","))), + }), + ), + }; ql::Class { qldoc: Some(String::from("The base class for all AST nodes")), name: "AstNode", is_abstract: false, - supertypes: vec![ql::Type::AtType("ast_node")].into_iter().collect(), + supertypes: vec![ql::Type::At(ast_node)].into_iter().collect(), characteristic_predicate: None, predicates: vec![ to_string, @@ -115,19 +117,20 @@ fn create_ast_node_class<'a>() -> ql::Class<'a> { get_parent_index, get_a_field_or_child, get_a_primary_ql_class, + get_primary_ql_classes, ], } } -fn create_token_class<'a>() -> ql::Class<'a> { - let tokeninfo_arity = 6; +pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Class<'a> { + let tokeninfo_arity = 4; let get_value = ql::Predicate { qldoc: Some(String::from("Gets the value of this token.")), name: "getValue", overridden: false, return_type: Some(ql::Type::String), formal_parameters: vec![], - body: create_get_field_expr_for_column_storage("result", "tokeninfo", 3, tokeninfo_arity), + body: create_get_field_expr_for_column_storage("result", tokeninfo, 1, tokeninfo_arity), }; let get_location = ql::Predicate { qldoc: Some(String::from("Gets the location of this token.")), @@ -135,7 +138,7 @@ fn create_token_class<'a>() -> ql::Class<'a> { overridden: true, return_type: Some(ql::Type::Normal("Location")), formal_parameters: vec![], - body: create_get_field_expr_for_column_storage("result", "tokeninfo", 4, tokeninfo_arity), + body: create_get_field_expr_for_column_storage("result", tokeninfo, 2, tokeninfo_arity), }; let to_string = ql::Predicate { qldoc: Some(String::from( @@ -147,14 +150,18 @@ fn create_token_class<'a>() -> ql::Class<'a> { formal_parameters: vec![], body: ql::Expression::Equals( Box::new(ql::Expression::Var("result")), - Box::new(ql::Expression::Pred("getValue", vec![])), + Box::new(ql::Expression::Dot( + Box::new(ql::Expression::Var("this")), + "getValue", + vec![], + )), ), }; ql::Class { qldoc: Some(String::from("A token.")), name: "Token", is_abstract: false, - supertypes: vec![ql::Type::AtType("token"), ql::Type::Normal("AstNode")] + supertypes: vec![ql::Type::At(token_type), ql::Type::Normal("AstNode")] .into_iter() .collect(), characteristic_predicate: None, @@ -168,15 +175,14 @@ fn create_token_class<'a>() -> ql::Class<'a> { } // Creates the `ReservedWord` class. -fn create_reserved_word_class<'a>() -> ql::Class<'a> { - let db_name = "reserved_word"; +pub fn create_reserved_word_class(db_name: &str) -> ql::Class { let class_name = "ReservedWord"; - let get_a_primary_ql_class = create_get_a_primary_ql_class(&class_name); + let get_a_primary_ql_class = create_get_a_primary_ql_class(class_name); ql::Class { qldoc: Some(String::from("A reserved word.")), name: class_name, is_abstract: false, - supertypes: vec![ql::Type::AtType(db_name), ql::Type::Normal("Token")] + supertypes: vec![ql::Type::At(db_name), ql::Type::Normal("Token")] .into_iter() .collect(), characteristic_predicate: None, @@ -192,8 +198,8 @@ fn create_none_predicate<'a>( return_type: Option>, ) -> ql::Predicate<'a> { ql::Predicate { - qldoc: qldoc, - name: name, + qldoc, + name, overridden, return_type, formal_parameters: Vec::new(), @@ -203,7 +209,7 @@ fn create_none_predicate<'a>( /// Creates an overridden `getAPrimaryQlClass` predicate that returns the given /// name. -fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> { +fn create_get_a_primary_ql_class(class_name: &str) -> ql::Predicate { ql::Predicate { qldoc: Some(String::from( "Gets the name of the primary QL class for this element.", @@ -225,7 +231,7 @@ fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> { /// /// `def_table` - the name of the table that defines the entity and its location. /// `arity` - the total number of columns in the table -fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Predicate<'a> { +fn create_get_location_predicate(def_table: &str, arity: usize) -> ql::Predicate { ql::Predicate { qldoc: Some(String::from("Gets the location of this element.")), name: "getLocation", @@ -250,7 +256,7 @@ fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Pr /// # Arguments /// /// `def_table` - the name of the table that defines the entity and its text. -fn create_get_text_predicate<'a>(def_table: &'a str) -> ql::Predicate<'a> { +fn create_get_text_predicate(def_table: &str) -> ql::Predicate { ql::Predicate { qldoc: Some(String::from("Gets the text content of this element.")), name: "getText", @@ -341,13 +347,13 @@ fn create_field_getters<'a>( ) -> (ql::Predicate<'a>, Option>) { let return_type = match &field.type_info { node_types::FieldTypeInfo::Single(t) => { - Some(ql::Type::Normal(&nodes.get(&t).unwrap().ql_class_name)) + Some(ql::Type::Normal(&nodes.get(t).unwrap().ql_class_name)) } node_types::FieldTypeInfo::Multiple { types: _, dbscheme_union: _, ql_class, - } => Some(ql::Type::Normal(&ql_class)), + } => Some(ql::Type::Normal(ql_class)), node_types::FieldTypeInfo::ReservedWordInt(_) => Some(ql::Type::String), }; let formal_parameters = match &field.storage { @@ -383,13 +389,13 @@ fn create_field_getters<'a>( ( create_get_field_expr_for_column_storage( get_value_result_var_name, - &main_table_name, + main_table_name, column_index, main_table_arity, ), create_get_field_expr_for_column_storage( get_value_result_var_name, - &main_table_name, + main_table_name, column_index, main_table_arity, ), @@ -402,12 +408,12 @@ fn create_field_getters<'a>( } => ( create_get_field_expr_for_table_storage( get_value_result_var_name, - &field_table_name, + field_table_name, if *has_index { Some("i") } else { None }, ), create_get_field_expr_for_table_storage( get_value_result_var_name, - &field_table_name, + field_table_name, if *has_index { Some("_") } else { None }, ), ), @@ -434,15 +440,16 @@ fn create_field_getters<'a>( }) .collect(); ( - ql::Expression::Aggregate( - "exists", - vec![ql::FormalParameter { + ql::Expression::Aggregate { + name: "exists", + vars: vec![ql::FormalParameter { name: "value", param_type: ql::Type::Int, }], - Box::new(get_value), - Box::new(ql::Expression::Or(disjuncts)), - ), + range: Some(Box::new(get_value)), + expr: Box::new(ql::Expression::Or(disjuncts)), + second_expr: None, + }, // Since the getter returns a string and not an AstNode, it won't be part of getAFieldOrChild: None, ) @@ -452,11 +459,9 @@ fn create_field_getters<'a>( } }; let qldoc = match &field.name { - Some(name) => { - format!("Gets the node corresponding to the field `{}`.", name) - } + Some(name) => format!("Gets the node corresponding to the field `{}`.", name), None => { - if formal_parameters.len() == 0 { + if formal_parameters.is_empty() { "Gets the child of this node.".to_owned() } else { "Gets the `i`th child of this node.".to_owned() @@ -477,14 +482,8 @@ fn create_field_getters<'a>( } /// Converts the given node types into CodeQL classes wrapping the dbscheme. -pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec> { - let mut classes: Vec = vec![ - ql::TopLevel::Import("codeql.files.FileSystem"), - ql::TopLevel::Import("codeql.Locations"), - ql::TopLevel::Class(create_ast_node_class()), - ql::TopLevel::Class(create_token_class()), - ql::TopLevel::Class(create_reserved_word_class()), - ]; +pub fn convert_nodes(nodes: &node_types::NodeTypeMap) -> Vec { + let mut classes: Vec = Vec::new(); let mut token_kinds = BTreeSet::new(); for (type_name, node) in nodes { if let node_types::EntryKind::Token { .. } = &node.kind { @@ -500,7 +499,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec = BTreeSet::new(); - supertypes.insert(ql::Type::AtType(&node.dbscheme_name)); + supertypes.insert(ql::Type::At(&node.dbscheme_name)); supertypes.insert(ql::Type::Normal("Token")); classes.push(ql::TopLevel::Class(ql::Class { qldoc: Some(format!("A class representing `{}` tokens.", type_name.kind)), @@ -520,7 +519,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec(nodes: &'a node_types::NodeTypeMap) -> Vec = Vec::new(); @@ -580,7 +579,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec std::io::Result { +pub fn read_node_types(prefix: &str, node_types_path: &Path) -> std::io::Result { let file = fs::File::open(node_types_path)?; - let node_types = serde_json::from_reader(file)?; - Ok(convert_nodes(&node_types)) + let node_types: Vec = serde_json::from_reader(file)?; + Ok(convert_nodes(prefix, &node_types)) } -pub fn read_node_types_str(node_types_json: &str) -> std::io::Result { - let node_types = serde_json::from_str(node_types_json)?; - Ok(convert_nodes(&node_types)) +pub fn read_node_types_str(prefix: &str, node_types_json: &str) -> std::io::Result { + let node_types: Vec = serde_json::from_str(node_types_json)?; + Ok(convert_nodes(prefix, &node_types)) } fn convert_type(node_type: &NodeType) -> TypeName { @@ -99,32 +99,33 @@ fn convert_type(node_type: &NodeType) -> TypeName { } } -fn convert_types(node_types: &Vec) -> Set { - let iter = node_types.iter().map(convert_type).collect(); - std::collections::BTreeSet::from(iter) +fn convert_types(node_types: &[NodeType]) -> Set { + node_types.iter().map(convert_type).collect() } -pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { +pub fn convert_nodes(prefix: &str, nodes: &[NodeInfo]) -> NodeTypeMap { let mut entries = NodeTypeMap::new(); let mut token_kinds = Set::new(); // First, find all the token kinds for node in nodes { - if node.subtypes.is_none() { - if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() { - let type_name = TypeName { - kind: node.kind.clone(), - named: node.named, - }; - token_kinds.insert(type_name); - } + if node.subtypes.is_none() + && node.fields.as_ref().map_or(0, |x| x.len()) == 0 + && node.children.is_none() + { + let type_name = TypeName { + kind: node.kind.clone(), + named: node.named, + }; + token_kinds.insert(type_name); } } for node in nodes { let flattened_name = &node_type_name(&node.kind, node.named); - let dbscheme_name = escape_name(&flattened_name); + let dbscheme_name = escape_name(flattened_name); let ql_class_name = dbscheme_name_to_class_name(&dbscheme_name); + let dbscheme_name = format!("{}_{}", prefix, &dbscheme_name); if let Some(subtypes) = &node.subtypes { // It's a tree-sitter supertype node, for which we create a union // type. @@ -137,7 +138,7 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { dbscheme_name, ql_class_name, kind: EntryKind::Union { - members: convert_types(&subtypes), + members: convert_types(subtypes), }, }, ); @@ -150,6 +151,8 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { named: node.named, }; let table_name = escape_name(&(format!("{}_def", &flattened_name))); + let table_name = format!("{}_{}", prefix, &table_name); + let mut fields = Vec::new(); // If the type also has fields or children, then we create either @@ -157,6 +160,7 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { if let Some(node_fields) = &node.fields { for (field_name, field_info) in node_fields { add_field( + prefix, &type_name, Some(field_name.to_string()), field_info, @@ -167,7 +171,14 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { } if let Some(children) = &node.children { // Treat children as if they were a field called 'child'. - add_field(&type_name, None, children, &mut fields, &token_kinds); + add_field( + prefix, + &type_name, + None, + children, + &mut fields, + &token_kinds, + ); } entries.insert( type_name, @@ -188,13 +199,13 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { counter += 1; let unprefixed_name = node_type_name(&type_name.kind, true); Entry { - dbscheme_name: escape_name(&format!("token_{}", &unprefixed_name)), + dbscheme_name: escape_name(&format!("{}_token_{}", &prefix, &unprefixed_name)), ql_class_name: dbscheme_name_to_class_name(&escape_name(&unprefixed_name)), kind: EntryKind::Token { kind_id: counter }, } } else { Entry { - dbscheme_name: "reserved_word".to_owned(), + dbscheme_name: format!("{}_reserved_word", &prefix), ql_class_name: "ReservedWord".to_owned(), kind: EntryKind::Token { kind_id: 0 }, } @@ -205,6 +216,7 @@ pub fn convert_nodes(nodes: &Vec) -> NodeTypeMap { } fn add_field( + prefix: &str, parent_type_name: &TypeName, field_name: Option, field_info: &FieldInfo, @@ -221,7 +233,8 @@ fn add_field( // Put the field in an auxiliary table. let has_index = field_info.multiple; let field_table_name = escape_name(&format!( - "{}_{}", + "{}_{}_{}", + &prefix, parent_flattened_name, &name_for_field_or_child(&field_name) )); @@ -240,13 +253,11 @@ fn add_field( // All possible types for this field are reserved words. The db // representation will be an `int` with a `case @foo.field = ...` to // enumerate the possible values. - let mut counter = 0; let mut field_token_ints: BTreeMap = BTreeMap::new(); - for t in converted_types { + for (counter, t) in converted_types.into_iter().enumerate() { let dbscheme_variant_name = - escape_name(&format!("{}_{}", parent_flattened_name, t.kind)); + escape_name(&format!("{}_{}_{}", &prefix, parent_flattened_name, t.kind)); field_token_ints.insert(t.kind.to_owned(), (counter, dbscheme_variant_name)); - counter += 1; } FieldTypeInfo::ReservedWordInt(field_token_ints) } else if field_info.types.len() == 1 { @@ -256,7 +267,8 @@ fn add_field( FieldTypeInfo::Multiple { types: converted_types, dbscheme_union: format!( - "{}_{}_type", + "{}_{}_{}_type", + &prefix, &parent_flattened_name, &name_for_field_or_child(&field_name) ), @@ -316,7 +328,7 @@ fn node_type_name(kind: &str, named: bool) -> String { } } -const RESERVED_KEYWORDS: [&'static str; 14] = [ +const RESERVED_KEYWORDS: [&str; 14] = [ "boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype", "type", "unique", "varchar", ]; @@ -380,6 +392,23 @@ fn escape_name(name: &str) -> String { result } +pub fn to_snake_case(word: &str) -> String { + let mut prev_upper = true; + let mut result = String::new(); + for c in word.chars() { + if c.is_uppercase() { + if !prev_upper { + result.push('_') + } + prev_upper = true; + result.push(c.to_ascii_lowercase()); + } else { + prev_upper = false; + result.push(c); + } + } + result +} /// Given a valid dbscheme name (i.e. in snake case), produces the equivalent QL /// name (i.e. in CamelCase). For example, "foo_bar_baz" becomes "FooBarBaz". fn dbscheme_name_to_class_name(dbscheme_name: &str) -> String { @@ -402,3 +431,10 @@ fn dbscheme_name_to_class_name(dbscheme_name: &str) -> String { .collect::>() .join("") } + +#[test] +fn to_snake_case_test() { + assert_eq!("python", to_snake_case("Python")); + assert_eq!("yaml", to_snake_case("YAML")); + assert_eq!("set_literal", to_snake_case("SetLiteral")); +} diff --git a/ql/src/codeql/files/FileSystem.qll b/ql/src/codeql/files/FileSystem.qll index 7573ff9e905..a03d030f1cb 100644 --- a/ql/src/codeql/files/FileSystem.qll +++ b/ql/src/codeql/files/FileSystem.qll @@ -156,7 +156,7 @@ abstract class Container extends @container { /** A folder. */ class Folder extends Container, @folder { - override string getAbsolutePath() { folders(this, result, _) } + override string getAbsolutePath() { folders(this, result) } /** Gets the URL of this folder. */ override string getURL() { result = "folder://" + getAbsolutePath() } @@ -164,21 +164,21 @@ class Folder extends Container, @folder { /** A file. */ class File extends Container, @file { - override string getAbsolutePath() { files(this, result, _, _, _) } + override string getAbsolutePath() { files(this, result) } /** Gets the URL of this file. */ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } /** Gets a token in this file. */ - private Generated::Token getAToken() { result.getLocation().getFile() = this } + private QL::Token getAToken() { result.getLocation().getFile() = this } /** Holds if `line` contains a token. */ private predicate line(int line, boolean comment) { - exists(Generated::Token token, Location l | + exists(QL::Token token, Location l | token = this.getAToken() and l = token.getLocation() and line in [l.getStartLine() .. l.getEndLine()] and - if token instanceof @token_block_comment or token instanceof @token_line_comment + if token instanceof @ql_token_block_comment or token instanceof @ql_token_line_comment then comment = true else comment = false ) @@ -194,5 +194,5 @@ class File extends Container, @file { int getNumberOfLinesOfComments() { result = count(int line | this.line(line, true)) } /** Holds if this file was extracted from ordinary source code. */ - predicate fromSource() { files(this, _, _, _, 1) } + predicate fromSource() { any() } } diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 2c93da2af47..718ca8707b1 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -26,8 +26,8 @@ class AstNode extends TAstNode { */ cached Location getLocation() { - exists(Generated::AstNode node | not node instanceof Generated::ParExpr | - node = toGenerated(this) and + exists(QL::AstNode node | not node instanceof QL::ParExpr | + node = toQL(this) and result = node.getLocation() ) } @@ -70,7 +70,7 @@ class AstNode extends TAstNode { predicate hasAnnotation(string name) { this.getAnAnnotation().getName() = name } /** Gets an annotation of this AST node. */ - Annotation getAnAnnotation() { toGenerated(this).getParent() = toGenerated(result).getParent() } + Annotation getAnAnnotation() { toQL(this).getParent() = toQL(result).getParent() } /** * Gets the predicate that contains this AST node. @@ -78,13 +78,13 @@ class AstNode extends TAstNode { pragma[noinline] Predicate getEnclosingPredicate() { not this instanceof Predicate and - toGenerated(result) = toGenerated(this).getParent+() + toQL(result) = toQL(this).getParent+() } } /** A toplevel QL program, i.e. a file. */ class TopLevel extends TTopLevel, AstNode { - Generated::Ql file; + QL::Ql file; TopLevel() { this = TTopLevel(file) } @@ -96,7 +96,7 @@ class TopLevel extends TTopLevel, AstNode { /** Gets the `i`'th member of this top-level module. */ ModuleMember getMember(int i) { - toGenerated(result) = file.getChild(i).(Generated::ModuleMember).getChild(_) + toQL(result) = file.getChild(i).(QL::ModuleMember).getChild(_) } /** Gets a top-level import in this module. */ @@ -138,7 +138,7 @@ class TopLevel extends TTopLevel, AstNode { } class QLDoc extends TQLDoc, AstNode { - Generated::Qldoc qldoc; + QL::Qldoc qldoc; QLDoc() { this = TQLDoc(qldoc) } @@ -151,28 +151,28 @@ class QLDoc extends TQLDoc, AstNode { * The `from, where, select` part of a QL query. */ class Select extends TSelect, AstNode { - Generated::Select sel; + QL::Select sel; Select() { this = TSelect(sel) } /** * Gets the `i`th variable in the `from` clause. */ - VarDecl getVarDecl(int i) { toGenerated(result) = sel.getChild(i) } + VarDecl getVarDecl(int i) { toQL(result) = sel.getChild(i) } /** * Gets the formula in the `where`. */ - Formula getWhere() { toGenerated(result) = sel.getChild(_) } + Formula getWhere() { toQL(result) = sel.getChild(_) } /** * Gets the `i`th expression in the `select` clause. */ - Expr getExpr(int i) { toGenerated(result) = sel.getChild(_).(Generated::AsExprs).getChild(i) } + Expr getExpr(int i) { toQL(result) = sel.getChild(_).(QL::AsExprs).getChild(i) } // TODO: This gets the `i`th order-by, but some expressions might not have an order-by. Expr getOrderBy(int i) { - toGenerated(result) = sel.getChild(_).(Generated::OrderBys).getChild(i).getChild(0) + toQL(result) = sel.getChild(_).(QL::OrderBys).getChild(i).getChild(0) } override AstNode getAChild(string pred) { @@ -306,7 +306,7 @@ class Predicate extends TPredicate, AstNode, PredicateOrBuiltin, Declaration { * A relation in the database. */ class Relation extends TDBRelation, AstNode, Declaration { - Generated::DbTable table; + QL::DbTable table; Relation() { this = TDBRelation(table) } @@ -315,9 +315,9 @@ class Relation extends TDBRelation, AstNode, Declaration { */ override string getName() { result = table.getTableName().getChild().getValue() } - private Generated::DbColumn getColumn(int i) { + private QL::DbColumn getColumn(int i) { result = - rank[i + 1](Generated::DbColumn column, int child | + rank[i + 1](QL::DbColumn column, int child | table.getChild(child) = column | column order by child @@ -330,7 +330,7 @@ class Relation extends TDBRelation, AstNode, Declaration { /** Gets the `i`th parameter type */ string getParameterType(int i) { // TODO: This is just using the name of the type, not the actual type. Checkout Type.qll - result = getColumn(i).getColType().getChild().(Generated::Token).getValue() + result = getColumn(i).getColType().getChild().(QL::Token).getValue() } /** @@ -345,7 +345,7 @@ class Relation extends TDBRelation, AstNode, Declaration { * An expression that refers to a predicate, e.g. `BasicBlock::succ/2`. */ class PredicateExpr extends TPredicateExpr, AstNode { - Generated::PredicateExpr pe; + QL::PredicateExpr pe; PredicateExpr() { this = TPredicateExpr(pe) } @@ -356,7 +356,7 @@ class PredicateExpr extends TPredicateExpr, AstNode { * E.g. for `BasicBlock::succ/2` the result is "succ". */ string getName() { - exists(Generated::AritylessPredicateExpr ape, Generated::LiteralId id | + exists(QL::AritylessPredicateExpr ape, QL::LiteralId id | ape.getParent() = pe and id.getParent() = ape and result = id.getValue() @@ -368,7 +368,7 @@ class PredicateExpr extends TPredicateExpr, AstNode { * E.g. for `BasicBlock::succ/2` the result is 2. */ int getArity() { - exists(Generated::Integer i | + exists(QL::Integer i | i.getParent() = pe and result = i.getValue().toInt() ) @@ -379,9 +379,9 @@ class PredicateExpr extends TPredicateExpr, AstNode { * E.g. for `BasicBlock::succ/2` the result is a `ModuleExpr` representing "BasicBlock". */ ModuleExpr getQualifier() { - exists(Generated::AritylessPredicateExpr ape | + exists(QL::AritylessPredicateExpr ape | ape.getParent() = pe and - toGenerated(result).getParent() = ape + toQL(result).getParent() = ape ) } @@ -403,7 +403,7 @@ class PredicateExpr extends TPredicateExpr, AstNode { * A classless predicate. */ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclaration { - Generated::ClasslessPredicate pred; + QL::ClasslessPredicate pred; ClasslessPredicate() { this = TClasslessPredicate(pred) } @@ -413,30 +413,30 @@ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclarati * The result is either a `PredicateExpr` or `HigherOrderFormula`. */ final AstNode getAlias() { - exists(Generated::PredicateAliasBody alias | + exists(QL::PredicateAliasBody alias | alias.getParent() = pred and - toGenerated(result).getParent() = alias + toQL(result).getParent() = alias ) or - toGenerated(result) = pred.getChild(_).(Generated::HigherOrderTerm) + toQL(result) = pred.getChild(_).(QL::HigherOrderTerm) } override string getAPrimaryQlClass() { result = "ClasslessPredicate" } - override Formula getBody() { toGenerated(result) = pred.getChild(_).(Generated::Body).getChild() } + override Formula getBody() { toQL(result) = pred.getChild(_).(QL::Body).getChild() } override string getName() { result = pred.getName().getValue() } override VarDecl getParameter(int i) { - toGenerated(result) = - rank[i + 1](Generated::VarDecl decl, int index | + toQL(result) = + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index ) } - override TypeExpr getReturnTypeExpr() { toGenerated(result) = pred.getReturnType() } + override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() } override AstNode getAChild(string pred_name) { result = Predicate.super.getAChild(pred_name) @@ -457,13 +457,13 @@ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclarati * A predicate in a class. */ class ClassPredicate extends TClassPredicate, Predicate { - Generated::MemberPredicate pred; + QL::MemberPredicate pred; ClassPredicate() { this = TClassPredicate(pred) } override string getName() { result = pred.getName().getValue() } - override Formula getBody() { toGenerated(result) = pred.getChild(_).(Generated::Body).getChild() } + override Formula getBody() { toQL(result) = pred.getChild(_).(QL::Body).getChild() } override string getAPrimaryQlClass() { result = "ClassPredicate" } @@ -475,8 +475,8 @@ class ClassPredicate extends TClassPredicate, Predicate { predicate isOverride() { hasAnnotation("override") } override VarDecl getParameter(int i) { - toGenerated(result) = - rank[i + 1](Generated::VarDecl decl, int index | + toQL(result) = + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index @@ -490,7 +490,7 @@ class ClassPredicate extends TClassPredicate, Predicate { predicate overrides(ClassPredicate other) { predOverrides(this, other) } - override TypeExpr getReturnTypeExpr() { toGenerated(result) = pred.getReturnType() } + override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() } override AstNode getAChild(string pred_name) { result = super.getAChild(pred_name) @@ -507,13 +507,13 @@ class ClassPredicate extends TClassPredicate, Predicate { * A characteristic predicate of a class. */ class CharPred extends TCharPred, Predicate { - Generated::Charpred pred; + QL::Charpred pred; CharPred() { this = TCharPred(pred) } override string getAPrimaryQlClass() { result = "CharPred" } - override Formula getBody() { toGenerated(result) = pred.getBody() } + override Formula getBody() { toQL(result) = pred.getBody() } override string getName() { result = getParent().(Class).getName() } @@ -545,11 +545,11 @@ class VarDef extends TVarDef, AstNode { * A variable declaration, with a type and a name. */ class VarDecl extends TVarDecl, VarDef, Declaration { - Generated::VarDecl var; + QL::VarDecl var; VarDecl() { this = TVarDecl(var) } - override string getName() { result = var.getChild(1).(Generated::VarName).getChild().getValue() } + override string getName() { result = var.getChild(1).(QL::VarName).getChild().getValue() } override Type getType() { result = this.getTypeExpr().getResolvedType() } @@ -558,15 +558,15 @@ class VarDecl extends TVarDecl, VarDef, Declaration { /** * Gets the type part of this variable declaration. */ - TypeExpr getTypeExpr() { toGenerated(result) = var.getChild(0) } + TypeExpr getTypeExpr() { toQL(result) = var.getChild(0) } /** * Holds if this variable declaration is a private field on a class. */ predicate isPrivate() { - exists(Generated::ClassMember member | - var = member.getChild(_).(Generated::Field).getChild() and - member.getAFieldOrChild().(Generated::Annotation).getName().getValue() = "private" + exists(QL::ClassMember member | + var = member.getChild(_).(QL::Field).getChild() and + member.getAFieldOrChild().(QL::Annotation).getName().getValue() = "private" ) } @@ -591,7 +591,7 @@ class VarDecl extends TVarDecl, VarDef, Declaration { * A type reference, such as `DataFlow::Node`. */ class TypeExpr extends TType, AstNode { - Generated::TypeExpr type; + QL::TypeExpr type; TypeExpr() { this = TType(type) } @@ -606,26 +606,26 @@ class TypeExpr extends TType, AstNode { string getClassName() { result = type.getName().getValue() or - result = type.getChild().(Generated::PrimitiveType).getValue() + result = type.getChild().(QL::PrimitiveType).getValue() or - result = type.getChild().(Generated::Dbtype).getValue() + result = type.getChild().(QL::Dbtype).getValue() } /** * Holds if this type is a primitive such as `string` or `int`. */ - predicate isPrimitive() { type.getChild() instanceof Generated::PrimitiveType } + predicate isPrimitive() { type.getChild() instanceof QL::PrimitiveType } /** * Holds if this type is a db-type. */ - predicate isDBType() { type.getChild() instanceof Generated::Dbtype } + predicate isDBType() { type.getChild() instanceof QL::Dbtype } /** * Gets the module of the type, if it exists. * E.g. `DataFlow` in `DataFlow::Node`. */ - ModuleExpr getModule() { toGenerated(result) = type.getChild() } + ModuleExpr getModule() { toQL(result) = type.getChild() } /** * Gets the type that this type reference refers to. @@ -643,23 +643,23 @@ class TypeExpr extends TType, AstNode { * A QL module. */ class Module extends TModule, ModuleDeclaration { - Generated::Module mod; + QL::Module mod; Module() { this = TModule(mod) } override string getAPrimaryQlClass() { result = "Module" } - override string getName() { result = mod.getName().(Generated::ModuleName).getChild().getValue() } + override string getName() { result = mod.getName().(QL::ModuleName).getChild().getValue() } /** * Gets a member of the module. */ AstNode getAMember() { - toGenerated(result) = mod.getChild(_).(Generated::ModuleMember).getChild(_) + toQL(result) = mod.getChild(_).(QL::ModuleMember).getChild(_) } AstNode getMember(int i) { - toGenerated(result) = mod.getChild(i).(Generated::ModuleMember).getChild(_) + toQL(result) = mod.getChild(i).(QL::ModuleMember).getChild(_) } QLDoc getQLDocFor(AstNode m) { @@ -668,7 +668,7 @@ class Module extends TModule, ModuleDeclaration { /** Gets the module expression that this module is an alias for, if any. */ ModuleExpr getAlias() { - toGenerated(result) = mod.getAFieldOrChild().(Generated::ModuleAliasBody).getChild() + toQL(result) = mod.getAFieldOrChild().(QL::ModuleAliasBody).getChild() } override AstNode getAChild(string pred) { @@ -720,7 +720,7 @@ class TypeDeclaration extends TTypeDeclaration, Declaration { } * A QL class. */ class Class extends TClass, TypeDeclaration, ModuleDeclaration { - Generated::Dataclass cls; + QL::Dataclass cls; Class() { this = TClass(cls) } @@ -732,13 +732,13 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * Gets the charateristic predicate for this class. */ CharPred getCharPred() { - toGenerated(result) = cls.getChild(_).(Generated::ClassMember).getChild(_) + toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) } AstNode getMember(int i) { - toGenerated(result) = cls.getChild(i).(Generated::ClassMember).getChild(_) or - toGenerated(result) = - cls.getChild(i).(Generated::ClassMember).getChild(_).(Generated::Field).getChild() + toQL(result) = cls.getChild(i).(QL::ClassMember).getChild(_) or + toQL(result) = + cls.getChild(i).(QL::ClassMember).getChild(_).(QL::Field).getChild() } QLDoc getQLDocFor(AstNode m) { @@ -749,7 +749,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * Gets a predicate in this class. */ ClassPredicate getAClassPredicate() { - toGenerated(result) = cls.getChild(_).(Generated::ClassMember).getChild(_) + toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) } /** @@ -764,23 +764,23 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * Gets a field in this class. */ VarDecl getAField() { - toGenerated(result) = - cls.getChild(_).(Generated::ClassMember).getChild(_).(Generated::Field).getChild() + toQL(result) = + cls.getChild(_).(QL::ClassMember).getChild(_).(QL::Field).getChild() } /** * Gets a super-type referenced in the `extends` part of the class declaration. */ - TypeExpr getASuperType() { toGenerated(result) = cls.getExtends(_) } + TypeExpr getASuperType() { toQL(result) = cls.getExtends(_) } /** Gets the type that this class is defined to be an alias of. */ TypeExpr getAliasType() { - toGenerated(result) = cls.getChild(_).(Generated::TypeAliasBody).getChild() + toQL(result) = cls.getChild(_).(QL::TypeAliasBody).getChild() } /** Gets the type of one of the members that this class is defined to be a union of. */ TypeExpr getUnionMember() { - toGenerated(result) = cls.getChild(_).(Generated::TypeUnionBody).getChild(_) + toQL(result) = cls.getChild(_).(QL::TypeUnionBody).getChild(_) } /** Gets the class type defined by this class declaration. */ @@ -810,7 +810,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * A `newtype Foo` declaration. */ class NewType extends TNewType, TypeDeclaration, ModuleDeclaration { - Generated::Datatype type; + QL::Datatype type; NewType() { this = TNewType(type) } @@ -821,7 +821,7 @@ class NewType extends TNewType, TypeDeclaration, ModuleDeclaration { /** * Gets a branch in this `newtype`. */ - NewTypeBranch getABranch() { toGenerated(result) = type.getChild().getChild(_) } + NewTypeBranch getABranch() { toQL(result) = type.getChild().getChild(_) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -835,7 +835,7 @@ class NewType extends TNewType, TypeDeclaration, ModuleDeclaration { * E.g. `Bar()` or `Baz()` in `newtype Foo = Bar() or Baz()`. */ class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration { - Generated::DatatypeBranch branch; + QL::DatatypeBranch branch; NewTypeBranch() { this = TNewTypeBranch(branch) } @@ -845,8 +845,8 @@ class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration /** Gets a field in this branch. */ VarDecl getField(int i) { - toGenerated(result) = - rank[i + 1](Generated::VarDecl var, int index | + toQL(result) = + rank[i + 1](QL::VarDecl var, int index | var = branch.getChild(index) | var order by index @@ -854,7 +854,7 @@ class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration } /** Gets the body of this branch. */ - Formula getBody() { toGenerated(result) = branch.getChild(_).(Generated::Body).getChild() } + Formula getBody() { toQL(result) = branch.getChild(_).(QL::Body).getChild() } override NewTypeBranchType getReturnType() { result.getDeclaration() = this } @@ -866,7 +866,7 @@ class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration override predicate isPrivate() { this.getNewType().isPrivate() } - override QLDoc getQLDoc() { toGenerated(result) = branch.getChild(_) } + override QLDoc getQLDoc() { toQL(result) = branch.getChild(_) } NewType getNewType() { result.getABranch() = this } @@ -917,27 +917,27 @@ class Call extends TCall, Expr, Formula { * E.g. `foo()` or `Foo::bar()`. */ class PredicateCall extends TPredicateCall, Call { - Generated::CallOrUnqualAggExpr expr; + QL::CallOrUnqualAggExpr expr; PredicateCall() { this = TPredicateCall(expr) } override Expr getArgument(int i) { - exists(Generated::CallBody body | body.getParent() = expr | - toGenerated(result) = body.getChild(i) + exists(QL::CallBody body | body.getParent() = expr | + toQL(result) = body.getChild(i) ) } final override ModuleExpr getQualifier() { - exists(Generated::AritylessPredicateExpr ape | + exists(QL::AritylessPredicateExpr ape | ape.getParent() = expr and - toGenerated(result).getParent() = ape + toQL(result).getParent() = ape ) } override string getAPrimaryQlClass() { result = "PredicateCall" } override predicate isClosure(string kind) { - kind = expr.getChild(_).(Generated::Closure).getValue() + kind = expr.getChild(_).(QL::Closure).getValue() } /** @@ -945,7 +945,7 @@ class PredicateCall extends TPredicateCall, Call { * E.g. for `foo()` the result is "foo". */ string getPredicateName() { - result = expr.getChild(0).(Generated::AritylessPredicateExpr).getName().getValue() + result = expr.getChild(0).(QL::AritylessPredicateExpr).getName().getValue() } override AstNode getAChild(string pred) { @@ -962,7 +962,7 @@ class PredicateCall extends TPredicateCall, Call { * E.g. `foo.bar()`. */ class MemberCall extends TMemberCall, Call { - Generated::QualifiedExpr expr; + QL::QualifiedExpr expr; MemberCall() { this = TMemberCall(expr) } @@ -973,11 +973,11 @@ class MemberCall extends TMemberCall, Call { * E.g. for `foo.bar()` the result is "bar". */ string getMemberName() { - result = expr.getChild(_).(Generated::QualifiedRhs).getName().getValue() + result = expr.getChild(_).(QL::QualifiedRhs).getName().getValue() } override predicate isClosure(string kind) { - kind = expr.getChild(_).(Generated::QualifiedRhs).getChild(_).(Generated::Closure).getValue() + kind = expr.getChild(_).(QL::QualifiedRhs).getChild(_).(QL::Closure).getValue() } /** @@ -986,13 +986,13 @@ class MemberCall extends TMemberCall, Call { * Only yields a result if this is actually a `super` call. */ TypeExpr getSuperType() { - toGenerated(result) = expr.getChild(_).(Generated::SuperRef).getChild(0) + toQL(result) = expr.getChild(_).(QL::SuperRef).getChild(0) } override Expr getArgument(int i) { result = rank[i + 1](Expr e, int index | - toGenerated(e) = expr.getChild(_).(Generated::QualifiedRhs).getChild(index) + toQL(e) = expr.getChild(_).(QL::QualifiedRhs).getChild(index) | e order by index ) @@ -1002,7 +1002,7 @@ class MemberCall extends TMemberCall, Call { * Gets the base of the member call. * E.g. for `foo.(Bar).baz()` the result is `foo.(Bar)`. */ - Expr getBase() { toGenerated(result) = expr.getChild(0) } + Expr getBase() { toQL(result) = expr.getChild(0) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1019,7 +1019,7 @@ class MemberCall extends TMemberCall, Call { * A call to the special `none()` predicate. */ class NoneCall extends TNoneCall, Call, Formula { - Generated::SpecialCall call; + QL::SpecialCall call; NoneCall() { this = TNoneCall(call) } @@ -1032,7 +1032,7 @@ class NoneCall extends TNoneCall, Call, Formula { * A call to the special `any()` predicate. */ class AnyCall extends TAnyCall, Call { - Generated::Aggregate agg; + QL::Aggregate agg; AnyCall() { this = TAnyCall(agg) } @@ -1043,7 +1043,7 @@ class AnyCall extends TAnyCall, Call { * An inline cast, e.g. `foo.(Bar)`. */ class InlineCast extends TInlineCast, Expr { - Generated::QualifiedExpr expr; + QL::QualifiedExpr expr; InlineCast() { this = TInlineCast(expr) } @@ -1054,7 +1054,7 @@ class InlineCast extends TInlineCast, Expr { * E.g. for `foo.(Bar)` the result is `Bar`. */ TypeExpr getTypeExpr() { - toGenerated(result) = expr.getChild(_).(Generated::QualifiedRhs).getChild(_) + toQL(result) = expr.getChild(_).(QL::QualifiedRhs).getChild(_) } override Type getType() { result = this.getTypeExpr().getResolvedType() } @@ -1063,7 +1063,7 @@ class InlineCast extends TInlineCast, Expr { * Gets the expression being cast. * E.g. for `foo.(Bar)` the result is `foo`. */ - Expr getBase() { toGenerated(result) = expr.getChild(0) } + Expr getBase() { toQL(result) = expr.getChild(0) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1084,7 +1084,7 @@ class ModuleRef extends AstNode, TModuleRef { * An import statement. */ class Import extends TImport, ModuleMember, ModuleRef { - Generated::ImportDirective imp; + QL::ImportDirective imp; Import() { this = TImport(imp) } @@ -1097,7 +1097,7 @@ class Import extends TImport, ModuleMember, ModuleRef { * import semmle.javascript.dataflow.Configuration as Flow * ``` */ - string importedAs() { result = imp.getChild(1).(Generated::ModuleName).getChild().getValue() } + string importedAs() { result = imp.getChild(1).(QL::ModuleName).getChild().getValue() } /** * Gets the `i`th selected name from the imported module. @@ -1106,7 +1106,7 @@ class Import extends TImport, ModuleMember, ModuleRef { * It is true that `getSelectionName(0) = "Baz"` and `getSelectionName(1) = "Qux"`. */ string getSelectionName(int i) { - result = imp.getChild(0).(Generated::ImportModuleExpr).getName(i).getValue() + result = imp.getChild(0).(QL::ImportModuleExpr).getName(i).getValue() } /** @@ -1116,7 +1116,7 @@ class Import extends TImport, ModuleMember, ModuleRef { * It is true that `getQualifiedName(0) = "foo"` and `getQualifiedName(1) = "bar"`. */ string getQualifiedName(int i) { - result = imp.getChild(0).(Generated::ImportModuleExpr).getChild().getName(i).getValue() + result = imp.getChild(0).(QL::ImportModuleExpr).getChild().getName(i).getValue() } final override FileOrModule getResolvedModule() { resolve(this, result) } @@ -1127,14 +1127,14 @@ class Formula extends TFormula, AstNode { } /** An `and` formula, with 2 or more operands. */ class Conjunction extends TConjunction, AstNode, Formula { - Generated::Conjunction conj; + QL::Conjunction conj; Conjunction() { this = TConjunction(conj) } override string getAPrimaryQlClass() { result = "Conjunction" } /** Gets an operand to this formula. */ - Formula getAnOperand() { toGenerated(result) in [conj.getLeft(), conj.getRight()] } + Formula getAnOperand() { toQL(result) in [conj.getLeft(), conj.getRight()] } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1145,14 +1145,14 @@ class Conjunction extends TConjunction, AstNode, Formula { /** An `or` formula, with 2 or more operands. */ class Disjunction extends TDisjunction, AstNode, Formula { - Generated::Disjunction disj; + QL::Disjunction disj; Disjunction() { this = TDisjunction(disj) } override string getAPrimaryQlClass() { result = "Disjunction" } /** Gets an operand to this formula. */ - Formula getAnOperand() { toGenerated(result) in [disj.getLeft(), disj.getRight()] } + Formula getAnOperand() { toQL(result) in [disj.getLeft(), disj.getRight()] } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1165,7 +1165,7 @@ class Disjunction extends TDisjunction, AstNode, Formula { * A comparison operator, such as `<` or `=`. */ class ComparisonOp extends TComparisonOp, AstNode { - Generated::Compop op; + QL::Compop op; ComparisonOp() { this = TComparisonOp(op) } @@ -1182,7 +1182,7 @@ class ComparisonOp extends TComparisonOp, AstNode { * A literal expression, such as `6` or `true` or `"foo"`. */ class Literal extends TLiteral, Expr { - Generated::Literal lit; + QL::Literal lit; Literal() { this = TLiteral(lit) } @@ -1191,7 +1191,7 @@ class Literal extends TLiteral, Expr { /** A string literal. */ class String extends Literal { - String() { lit.getChild() instanceof Generated::String } + String() { lit.getChild() instanceof QL::String } override string getAPrimaryQlClass() { result = "String" } @@ -1199,7 +1199,7 @@ class String extends Literal { /** Gets the string value of this literal. */ string getValue() { - exists(string raw | raw = lit.getChild().(Generated::String).getValue() | + exists(string raw | raw = lit.getChild().(QL::String).getValue() | result = raw.substring(1, raw.length() - 1) ) } @@ -1207,39 +1207,39 @@ class String extends Literal { /** An integer literal. */ class Integer extends Literal { - Integer() { lit.getChild() instanceof Generated::Integer } + Integer() { lit.getChild() instanceof QL::Integer } override string getAPrimaryQlClass() { result = "Integer" } override PrimitiveType getType() { result.getName() = "int" } /** Gets the integer value of this literal. */ - int getValue() { result = lit.getChild().(Generated::Integer).getValue().toInt() } + int getValue() { result = lit.getChild().(QL::Integer).getValue().toInt() } } /** A float literal. */ class Float extends Literal { - Float() { lit.getChild() instanceof Generated::Float } + Float() { lit.getChild() instanceof QL::Float } override string getAPrimaryQlClass() { result = "Float" } override PrimitiveType getType() { result.getName() = "float" } /** Gets the float value of this literal. */ - float getValue() { result = lit.getChild().(Generated::Float).getValue().toFloat() } + float getValue() { result = lit.getChild().(QL::Float).getValue().toFloat() } } /** A boolean literal */ class Boolean extends Literal { - Generated::Bool bool; + QL::Bool bool; Boolean() { lit.getChild() = bool } /** Holds if the value is `true` */ - predicate isTrue() { bool.getChild() instanceof Generated::True } + predicate isTrue() { bool.getChild() instanceof QL::True } /** Holds if the value is `false` */ - predicate isFalse() { bool.getChild() instanceof Generated::False } + predicate isFalse() { bool.getChild() instanceof QL::False } override PrimitiveType getType() { result.getName() = "boolean" } @@ -1260,21 +1260,21 @@ class ComparisonSymbol extends string { /** A comparison formula, such as `x < 3` or `y = true`. */ class ComparisonFormula extends TComparisonFormula, Formula { - Generated::CompTerm comp; + QL::CompTerm comp; ComparisonFormula() { this = TComparisonFormula(comp) } /** Gets the left operand of this comparison. */ - Expr getLeftOperand() { toGenerated(result) = comp.getLeft() } + Expr getLeftOperand() { toQL(result) = comp.getLeft() } /** Gets the right operand of this comparison. */ - Expr getRightOperand() { toGenerated(result) = comp.getRight() } + Expr getRightOperand() { toQL(result) = comp.getRight() } /** Gets an operand of this comparison. */ Expr getAnOperand() { result in [getLeftOperand(), getRightOperand()] } /** Gets the operator of this comparison. */ - ComparisonOp getOperator() { toGenerated(result) = comp.getChild() } + ComparisonOp getOperator() { toQL(result) = comp.getChild() } /** Gets the symbol of this comparison (as a string). */ ComparisonSymbol getSymbol() { result = this.getOperator().getSymbol() } @@ -1294,36 +1294,36 @@ class ComparisonFormula extends TComparisonFormula, Formula { /** A quantifier formula, such as `exists` or `forall`. */ class Quantifier extends TQuantifier, Formula { - Generated::Quantified quant; + QL::Quantified quant; string kind; Quantifier() { - this = TQuantifier(quant) and kind = quant.getChild(0).(Generated::Quantifier).getValue() + this = TQuantifier(quant) and kind = quant.getChild(0).(QL::Quantifier).getValue() } /** Gets the ith variable declaration of this quantifier. */ VarDecl getArgument(int i) { i >= 1 and - toGenerated(result) = quant.getChild(i - 1) + toQL(result) = quant.getChild(i - 1) } /** Gets an argument of this quantifier. */ VarDecl getAnArgument() { result = this.getArgument(_) } /** Gets the formula restricting the range of this quantifier, if any. */ - Formula getRange() { toGenerated(result) = quant.getRange() } + Formula getRange() { toQL(result) = quant.getRange() } /** Holds if this quantifier has a range formula. */ predicate hasRange() { exists(this.getRange()) } /** Gets the main body of the quantifier. */ - Formula getFormula() { toGenerated(result) = quant.getFormula() } + Formula getFormula() { toQL(result) = quant.getFormula() } /** * Gets the expression of this quantifier, if the quantifier is * of the form `exists( expr )`. */ - Expr getExpr() { toGenerated(result) = quant.getExpr() } + Expr getExpr() { toQL(result) = quant.getExpr() } /** * Holds if this is the "expression only" form of an exists quantifier. @@ -1369,18 +1369,18 @@ class Forex extends Quantifier { /** A conditional formula, of the form `if a then b else c`. */ class IfFormula extends TIfFormula, Formula { - Generated::IfTerm ifterm; + QL::IfTerm ifterm; IfFormula() { this = TIfFormula(ifterm) } /** Gets the condition (the `if` part) of this formula. */ - Formula getCondition() { toGenerated(result) = ifterm.getCond() } + Formula getCondition() { toQL(result) = ifterm.getCond() } /** Gets the `then` part of this formula. */ - Formula getThenPart() { toGenerated(result) = ifterm.getFirst() } + Formula getThenPart() { toQL(result) = ifterm.getFirst() } /** Gets the `else` part of this formula. */ - Formula getElsePart() { toGenerated(result) = ifterm.getSecond() } + Formula getElsePart() { toQL(result) = ifterm.getSecond() } override string getAPrimaryQlClass() { result = "IfFormula" } @@ -1399,15 +1399,15 @@ class IfFormula extends TIfFormula, Formula { * An implication formula, of the form `foo implies bar`. */ class Implication extends TImplication, Formula { - Generated::Implication imp; + QL::Implication imp; Implication() { this = TImplication(imp) } /** Gets the left operand of this implication. */ - Formula getLeftOperand() { toGenerated(result) = imp.getLeft() } + Formula getLeftOperand() { toQL(result) = imp.getLeft() } /** Gets the right operand of this implication. */ - Formula getRightOperand() { toGenerated(result) = imp.getRight() } + Formula getRightOperand() { toQL(result) = imp.getRight() } override string getAPrimaryQlClass() { result = "Implication" } @@ -1424,15 +1424,15 @@ class Implication extends TImplication, Formula { * A type check formula, of the form `foo instanceof bar`. */ class InstanceOf extends TInstanceOf, Formula { - Generated::InstanceOf inst; + QL::InstanceOf inst; InstanceOf() { this = TInstanceOf(inst) } /** Gets the expression being checked. */ - Expr getExpr() { toGenerated(result) = inst.getChild(0) } + Expr getExpr() { toQL(result) = inst.getChild(0) } /** Gets the reference to the type being checked. */ - TypeExpr getType() { toGenerated(result) = inst.getChild(1) } + TypeExpr getType() { toQL(result) = inst.getChild(1) } override string getAPrimaryQlClass() { result = "InstanceOf" } @@ -1450,7 +1450,7 @@ class InstanceOf extends TInstanceOf, Formula { * The formula holds if the lhs is in the rhs. */ class InFormula extends TInFormula, Formula { - Generated::InExpr inexpr; + QL::InExpr inexpr; InFormula() { this = TInFormula(inexpr) } @@ -1458,13 +1458,13 @@ class InFormula extends TInFormula, Formula { * Gets the expression that is checked for membership. * E.g. for `foo in [2, 3]` the result is `foo`. */ - Expr getExpr() { toGenerated(result) = inexpr.getLeft() } + Expr getExpr() { toQL(result) = inexpr.getLeft() } /** * Gets the range for this in formula. * E.g. for `foo in [2, 3]` the result is `[2, 3]`. */ - Expr getRange() { toGenerated(result) = inexpr.getRight() } + Expr getRange() { toQL(result) = inexpr.getRight() } override string getAPrimaryQlClass() { result = "InFormula" } @@ -1482,7 +1482,7 @@ class InFormula extends TInFormula, Formula { * E.g. `fastTC(pathSucc/2)(n1, n2)`. */ class HigherOrderFormula extends THigherOrderFormula, Formula { - Generated::HigherOrderTerm hop; + QL::HigherOrderTerm hop; HigherOrderFormula() { this = THigherOrderFormula(hop) } @@ -1490,7 +1490,7 @@ class HigherOrderFormula extends THigherOrderFormula, Formula { * Gets the `i`th input to this higher-order formula. * E.g. for `fastTC(pathSucc/2)(n1, n2)` the result is `pathSucc/2`. */ - PredicateExpr getInput(int i) { toGenerated(result) = hop.getChild(i).(Generated::PredicateExpr) } + PredicateExpr getInput(int i) { toQL(result) = hop.getChild(i).(QL::PredicateExpr) } /** * Gets the number of inputs. @@ -1501,7 +1501,7 @@ class HigherOrderFormula extends THigherOrderFormula, Formula { * Gets the `i`th argument to this higher-order formula. * E.g. for `fastTC(pathSucc/2)(n1, n2)` the result is `n1` and `n2`. */ - Expr getArgument(int i) { toGenerated(result) = hop.getChild(i + getNumInputs()) } + Expr getArgument(int i) { toQL(result) = hop.getChild(i + getNumInputs()) } /** * Gets the name of this higher-order predicate. @@ -1525,7 +1525,7 @@ class HigherOrderFormula extends THigherOrderFormula, Formula { class Aggregate extends TAggregate, Expr { string getKind() { none() } - Generated::Aggregate getAggregate() { none() } + QL::Aggregate getAggregate() { none() } } /** @@ -1533,13 +1533,13 @@ class Aggregate extends TAggregate, Expr { * E.g. `min(getAPredicate().getArity())`. */ class ExprAggregate extends TExprAggregate, Aggregate { - Generated::Aggregate agg; - Generated::ExprAggregateBody body; + QL::Aggregate agg; + QL::ExprAggregateBody body; string kind; ExprAggregate() { this = TExprAggregate(agg) and - kind = agg.getChild(0).(Generated::AggId).getValue() and + kind = agg.getChild(0).(QL::AggId).getValue() and body = agg.getChild(_) } @@ -1549,23 +1549,23 @@ class ExprAggregate extends TExprAggregate, Aggregate { */ override string getKind() { result = kind } - override Generated::Aggregate getAggregate() { result = agg } + override QL::Aggregate getAggregate() { result = agg } /** * Gets the ith "as" expression of this aggregate, if any. */ - Expr getExpr(int i) { toGenerated(result) = body.getAsExprs().getChild(i) } + Expr getExpr(int i) { toQL(result) = body.getAsExprs().getChild(i) } /** * Gets the ith "order by" expression of this aggregate, if any. */ - Expr getOrderBy(int i) { toGenerated(result) = body.getOrderBys().getChild(i).getChild(0) } + Expr getOrderBy(int i) { toQL(result) = body.getOrderBys().getChild(i).getChild(0) } /** * Gets the direction (ascending or descending) of the ith "order by" expression of this aggregate. */ string getOrderbyDirection(int i) { - result = body.getOrderBys().getChild(i).getChild(1).(Generated::Direction).getValue() + result = body.getOrderBys().getChild(i).getChild(1).(QL::Direction).getValue() } override string getAPrimaryQlClass() { result = "ExprAggregate[" + kind + "]" } @@ -1596,13 +1596,13 @@ class ExprAggregate extends TExprAggregate, Aggregate { /** An aggregate expression, such as `count` or `sum`. */ class FullAggregate extends TFullAggregate, Aggregate { - Generated::Aggregate agg; + QL::Aggregate agg; string kind; - Generated::FullAggregateBody body; + QL::FullAggregateBody body; FullAggregate() { this = TFullAggregate(agg) and - kind = agg.getChild(0).(Generated::AggId).getValue() and + kind = agg.getChild(0).(QL::AggId).getValue() and body = agg.getChild(_) } @@ -1612,10 +1612,10 @@ class FullAggregate extends TFullAggregate, Aggregate { */ override string getKind() { result = kind } - override Generated::Aggregate getAggregate() { result = agg } + override QL::Aggregate getAggregate() { result = agg } /** Gets the ith declared argument of this quantifier. */ - VarDecl getArgument(int i) { toGenerated(result) = body.getChild(i) } + VarDecl getArgument(int i) { toQL(result) = body.getChild(i) } /** Gets an argument of this quantifier. */ VarDecl getAnArgument() { result = this.getArgument(_) } @@ -1623,23 +1623,23 @@ class FullAggregate extends TFullAggregate, Aggregate { /** * Gets the formula restricting the range of this quantifier, if any. */ - Formula getRange() { toGenerated(result) = body.getGuard() } + Formula getRange() { toQL(result) = body.getGuard() } /** * Gets the ith "as" expression of this aggregate, if any. */ - Expr getExpr(int i) { toGenerated(result) = body.getAsExprs().getChild(i) } + Expr getExpr(int i) { toQL(result) = body.getAsExprs().getChild(i) } /** * Gets the ith "order by" expression of this aggregate, if any. */ - Expr getOrderBy(int i) { toGenerated(result) = body.getOrderBys().getChild(i).getChild(0) } + Expr getOrderBy(int i) { toQL(result) = body.getOrderBys().getChild(i).getChild(0) } /** * Gets the direction (ascending or descending) of the ith "order by" expression of this aggregate. */ string getOrderbyDirection(int i) { - result = body.getOrderBys().getChild(i).getChild(1).(Generated::Direction).getValue() + result = body.getOrderBys().getChild(i).getChild(1).(QL::Direction).getValue() } override string getAPrimaryQlClass() { kind != "rank" and result = "FullAggregate[" + kind + "]" } @@ -1687,7 +1687,7 @@ class Rank extends Aggregate { /** * The `i` in `rank[i]( | | )`. */ - Expr getRankExpr() { toGenerated(result) = this.getAggregate().getChild(1) } + Expr getRankExpr() { toQL(result) = this.getAggregate().getChild(1) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1700,7 +1700,7 @@ class Rank extends Aggregate { * An "as" expression, such as `foo as bar`. */ class AsExpr extends TAsExpr, VarDef, Expr { - Generated::AsExpr asExpr; + QL::AsExpr asExpr; AsExpr() { this = TAsExpr(asExpr) } @@ -1714,13 +1714,13 @@ class AsExpr extends TAsExpr, VarDef, Expr { * Gets the name the inner expression gets "saved" under. * For example this is `bar` in the expression `foo as bar`. */ - string getAsName() { result = asExpr.getChild(1).(Generated::VarName).getChild().getValue() } + string getAsName() { result = asExpr.getChild(1).(QL::VarName).getChild().getValue() } /** * Gets the inner expression of the "as" expression. For example, this is `foo` in * the expression `foo as bar`. */ - Expr getInnerExpr() { toGenerated(result) = asExpr.getChild(0) } + Expr getInnerExpr() { toQL(result) = asExpr.getChild(0) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -1733,7 +1733,7 @@ class AsExpr extends TAsExpr, VarDef, Expr { * An identifier, such as `foo`. */ class Identifier extends TIdentifier, Expr { - Generated::Variable id; + QL::Variable id; Identifier() { this = TIdentifier(id) } @@ -1753,7 +1753,7 @@ class VarAccess extends Identifier { /** Gets the accessed variable. */ VarDef getDeclaration() { result = decl } - override string getName() { result = id.getChild().(Generated::VarName).getChild().getValue() } + override string getName() { result = id.getChild().(QL::VarName).getChild().getValue() } override Type getType() { result = this.getDeclaration().getType() } @@ -1769,7 +1769,7 @@ class FieldAccess extends Identifier { /** Gets the accessed field. */ VarDecl getDeclaration() { result = decl } - override string getName() { result = id.getChild().(Generated::VarName).getChild().getValue() } + override string getName() { result = id.getChild().(QL::VarName).getChild().getValue() } override Type getType() { result = this.getDeclaration().getType() } @@ -1778,7 +1778,7 @@ class FieldAccess extends Identifier { /** An access to `this`. */ class ThisAccess extends Identifier { - ThisAccess() { any(Generated::This t).getParent() = id } + ThisAccess() { any(QL::This t).getParent() = id } override Type getType() { result = this.getParent+().(Class).getType() } @@ -1796,7 +1796,7 @@ class Super extends TSuper, Expr { /** An access to `result`. */ class ResultAccess extends Identifier { - ResultAccess() { any(Generated::Result r).getParent() = id } + ResultAccess() { any(QL::Result r).getParent() = id } override Type getType() { result = this.getParent+().(Predicate).getReturnType() } @@ -1807,12 +1807,12 @@ class ResultAccess extends Identifier { /** A `not` formula. */ class Negation extends TNegation, Formula { - Generated::Negation neg; + QL::Negation neg; Negation() { this = TNegation(neg) } /** Gets the formula being negated. */ - Formula getFormula() { toGenerated(result) = neg.getChild() } + Formula getFormula() { toQL(result) = neg.getChild() } override string getAPrimaryQlClass() { result = "Negation" } @@ -1830,7 +1830,7 @@ class Expr extends TExpr, AstNode { /** An expression annotation, such as `pragma[only_bind_into](config)`. */ class ExprAnnotation extends TExprAnnotation, Expr { - Generated::ExprAnnotation expr_anno; + QL::ExprAnnotation expr_anno; ExprAnnotation() { this = TExprAnnotation(expr_anno) } @@ -1850,7 +1850,7 @@ class ExprAnnotation extends TExprAnnotation, Expr { * Gets the inner expression. * E.g. for `pragma[only_bind_into](config)` the result is `config`. */ - Expr getExpression() { toGenerated(result) = expr_anno.getChild() } + Expr getExpression() { toQL(result) = expr_anno.getChild() } override Type getType() { result = this.getExpression().getType() } @@ -1889,14 +1889,14 @@ class BinOpExpr extends TBinOpExpr, Expr { * An addition or subtraction expression. */ class AddSubExpr extends TAddSubExpr, BinOpExpr { - Generated::AddExpr expr; + QL::AddExpr expr; FunctionSymbol operator; AddSubExpr() { this = TAddSubExpr(expr) and operator = expr.getChild().getValue() } - override Expr getLeftOperand() { toGenerated(result) = expr.getLeft() } + override Expr getLeftOperand() { toQL(result) = expr.getLeft() } - override Expr getRightOperand() { toGenerated(result) = expr.getRight() } + override Expr getRightOperand() { toQL(result) = expr.getRight() } override FunctionSymbol getOperator() { result = operator } @@ -1952,16 +1952,16 @@ class SubExpr extends AddSubExpr { * A multiplication, division, or modulo expression. */ class MulDivModExpr extends TMulDivModExpr, BinOpExpr { - Generated::MulExpr expr; + QL::MulExpr expr; FunctionSymbol operator; MulDivModExpr() { this = TMulDivModExpr(expr) and operator = expr.getChild().getValue() } /** Gets the left operand of the binary expression. */ - override Expr getLeftOperand() { toGenerated(result) = expr.getLeft() } + override Expr getLeftOperand() { toQL(result) = expr.getLeft() } /** Gets the right operand of the binary expression. */ - override Expr getRightOperand() { toGenerated(result) = expr.getRight() } + override Expr getRightOperand() { toQL(result) = expr.getRight() } override FunctionSymbol getOperator() { result = operator } @@ -2022,19 +2022,19 @@ class ModExpr extends MulDivModExpr { * A range expression, such as `[1 .. 10]`. */ class Range extends TRange, Expr { - Generated::Range range; + QL::Range range; Range() { this = TRange(range) } /** * Gets the lower bound of the range. */ - Expr getLowEndpoint() { toGenerated(result) = range.getLower() } + Expr getLowEndpoint() { toQL(result) = range.getLower() } /** * Gets the upper bound of the range. */ - Expr getHighEndpoint() { toGenerated(result) = range.getUpper() } + Expr getHighEndpoint() { toQL(result) = range.getUpper() } /** * Gets the lower and upper bounds of the range. @@ -2058,14 +2058,14 @@ class Range extends TRange, Expr { * A set literal expression, such as `[1,3,5,7]`. */ class Set extends TSet, Expr { - Generated::SetLiteral set; + QL::SetLiteral set; Set() { this = TSet(set) } /** * Gets the `i`th element in this set literal expression. */ - Expr getElement(int i) { toGenerated(result) = set.getChild(i) } + Expr getElement(int i) { toQL(result) = set.getChild(i) } /** * Gets an element in this set literal expression, if any. @@ -2090,12 +2090,12 @@ class Set extends TSet, Expr { /** A unary operation expression, such as `-(x*y)` */ class UnaryExpr extends TUnaryExpr, Expr { - Generated::UnaryExpr unaryexpr; + QL::UnaryExpr unaryexpr; UnaryExpr() { this = TUnaryExpr(unaryexpr) } /** Gets the operand of the unary expression. */ - Expr getOperand() { toGenerated(result) = unaryexpr.getChild(1) } + Expr getOperand() { toQL(result) = unaryexpr.getChild(1) } /** Gets the operator of the unary expression as a string. */ FunctionSymbol getOperator() { result = unaryexpr.getChild(0).toString() } @@ -2113,7 +2113,7 @@ class UnaryExpr extends TUnaryExpr, Expr { /** A "don't care" expression, denoted by `_`. */ class DontCare extends TDontCare, Expr { - Generated::Underscore dontcare; + QL::Underscore dontcare; DontCare() { this = TDontCare(dontcare) } @@ -2124,7 +2124,7 @@ class DontCare extends TDontCare, Expr { /** A module expression. Such as `DataFlow` in `DataFlow::Node` */ class ModuleExpr extends TModuleExpr, ModuleRef { - Generated::ModuleExpr me; + QL::ModuleExpr me; ModuleExpr() { this = TModuleExpr(me) } @@ -2140,7 +2140,7 @@ class ModuleExpr extends TModuleExpr, ModuleRef { string getName() { result = me.getName().getValue() or - not exists(me.getName()) and result = me.getChild().(Generated::SimpleId).getValue() + not exists(me.getName()) and result = me.getChild().(QL::SimpleId).getValue() } /** @@ -2169,7 +2169,7 @@ class ModuleExpr extends TModuleExpr, ModuleRef { /** An argument to an annotation. */ private class AnnotationArg extends TAnnotationArg, AstNode { - Generated::AnnotArg arg; + QL::AnnotArg arg; AnnotationArg() { this = TAnnotationArg(arg) } @@ -2177,8 +2177,8 @@ private class AnnotationArg extends TAnnotationArg, AstNode { string getValue() { result = [ - arg.getChild().(Generated::SimpleId).getValue(), - arg.getChild().(Generated::Result).getValue(), arg.getChild().(Generated::This).getValue() + arg.getChild().(QL::SimpleId).getValue(), + arg.getChild().(QL::Result).getValue(), arg.getChild().(QL::This).getValue() ] } @@ -2207,7 +2207,7 @@ private class MonotonicAggregatesArg extends AnnotationArg { /** An annotation on an element. */ class Annotation extends TAnnotation, AstNode { - Generated::Annotation annot; + QL::Annotation annot; Annotation() { this = TAnnotation(annot) } @@ -2218,7 +2218,7 @@ class Annotation extends TAnnotation, AstNode { override Location getLocation() { result = annot.getLocation() } /** Gets the node corresponding to the field `args`. */ - AnnotationArg getArgs(int i) { toGenerated(result) = annot.getArgs(i) } + AnnotationArg getArgs(int i) { toQL(result) = annot.getArgs(i) } /** Gets the node corresponding to the field `name`. */ string getName() { result = annot.getName().getValue() } @@ -2285,7 +2285,7 @@ module YAML { /** A YAML comment. */ class YAMLComment extends TYamlCommemt, YAMLNode { - Generated::YamlComment yamlcomment; + QL::YamlComment yamlcomment; YAMLComment() { this = TYamlCommemt(yamlcomment) } @@ -2294,13 +2294,13 @@ module YAML { /** A YAML entry. */ class YAMLEntry extends TYamlEntry, YAMLNode { - Generated::YamlEntry yamle; + QL::YamlEntry yamle; YAMLEntry() { this = TYamlEntry(yamle) } /** Gets the key of this YAML entry. */ YAMLKey getKey() { - exists(Generated::YamlKeyvaluepair pair | + exists(QL::YamlKeyvaluepair pair | pair.getParent() = yamle and result = TYamlKey(pair.getKey()) ) @@ -2308,7 +2308,7 @@ module YAML { /** Gets the value of this YAML entry. */ YAMLValue getValue() { - exists(Generated::YamlKeyvaluepair pair | + exists(QL::YamlKeyvaluepair pair | pair.getParent() = yamle and result = TYamlValue(pair.getValue()) ) @@ -2319,7 +2319,7 @@ module YAML { /** A YAML key. */ class YAMLKey extends TYamlKey, YAMLNode { - Generated::YamlKey yamlkey; + QL::YamlKey yamlkey; YAMLKey() { this = TYamlKey(yamlkey) } @@ -2327,7 +2327,7 @@ module YAML { * Gets the value of this YAML key. */ YAMLValue getValue() { - exists(Generated::YamlKeyvaluepair pair | + exists(QL::YamlKeyvaluepair pair | pair.getKey() = yamlkey and result = TYamlValue(pair.getValue()) ) } @@ -2336,7 +2336,7 @@ module YAML { /** Gets the value of this YAML value. */ string getNamePart(int i) { - i = 0 and result = yamlkey.getChild(0).(Generated::SimpleId).getValue() + i = 0 and result = yamlkey.getChild(0).(QL::SimpleId).getValue() or exists(YAMLKey child | child = TYamlKey(yamlkey.getChild(1)) and @@ -2355,7 +2355,7 @@ module YAML { /** A YAML list item. */ class YAMLListItem extends TYamlListitem, YAMLNode { - Generated::YamlListitem yamllistitem; + QL::YamlListitem yamllistitem; YAMLListItem() { this = TYamlListitem(yamllistitem) } @@ -2369,7 +2369,7 @@ module YAML { /** A YAML value. */ class YAMLValue extends TYamlValue, YAMLNode { - Generated::YamlValue yamlvalue; + QL::YamlValue yamlvalue; YAMLValue() { this = TYamlValue(yamlvalue) } diff --git a/ql/src/codeql_ql/ast/internal/AstNodes.qll b/ql/src/codeql_ql/ast/internal/AstNodes.qll index c7e73105b4c..8aab6db4189 100644 --- a/ql/src/codeql_ql/ast/internal/AstNodes.qll +++ b/ql/src/codeql_ql/ast/internal/AstNodes.qll @@ -4,69 +4,69 @@ private import Builtins cached newtype TAstNode = - TTopLevel(Generated::Ql file) or - TQLDoc(Generated::Qldoc qldoc) or - TClasslessPredicate(Generated::ClasslessPredicate pred) or - TVarDecl(Generated::VarDecl decl) or - TClass(Generated::Dataclass dc) or - TCharPred(Generated::Charpred pred) or - TClassPredicate(Generated::MemberPredicate pred) or - TDBRelation(Generated::DbTable table) or - TSelect(Generated::Select sel) or - TModule(Generated::Module mod) or - TNewType(Generated::Datatype dt) or - TNewTypeBranch(Generated::DatatypeBranch branch) or - TImport(Generated::ImportDirective imp) or - TType(Generated::TypeExpr type) or - TDisjunction(Generated::Disjunction disj) or - TConjunction(Generated::Conjunction conj) or - TComparisonFormula(Generated::CompTerm comp) or - TComparisonOp(Generated::Compop op) or - TQuantifier(Generated::Quantified quant) or - TFullAggregate(Generated::Aggregate agg) { - agg.getChild(_) instanceof Generated::FullAggregateBody + TTopLevel(QL::Ql file) or + TQLDoc(QL::Qldoc qldoc) or + TClasslessPredicate(QL::ClasslessPredicate pred) or + TVarDecl(QL::VarDecl decl) or + TClass(QL::Dataclass dc) or + TCharPred(QL::Charpred pred) or + TClassPredicate(QL::MemberPredicate pred) or + TDBRelation(QL::DbTable table) or + TSelect(QL::Select sel) or + TModule(QL::Module mod) or + TNewType(QL::Datatype dt) or + TNewTypeBranch(QL::DatatypeBranch branch) or + TImport(QL::ImportDirective imp) or + TType(QL::TypeExpr type) or + TDisjunction(QL::Disjunction disj) or + TConjunction(QL::Conjunction conj) or + TComparisonFormula(QL::CompTerm comp) or + TComparisonOp(QL::Compop op) or + TQuantifier(QL::Quantified quant) or + TFullAggregate(QL::Aggregate agg) { + agg.getChild(_) instanceof QL::FullAggregateBody } or - TExprAggregate(Generated::Aggregate agg) { - agg.getChild(_) instanceof Generated::ExprAggregateBody + TExprAggregate(QL::Aggregate agg) { + agg.getChild(_) instanceof QL::ExprAggregateBody } or - TSuper(Generated::SuperRef sup) or - TIdentifier(Generated::Variable var) or - TAsExpr(Generated::AsExpr asExpr) { asExpr.getChild(1) instanceof Generated::VarName } or - TPredicateCall(Generated::CallOrUnqualAggExpr call) or - TMemberCall(Generated::QualifiedExpr expr) { - not expr.getChild(_).(Generated::QualifiedRhs).getChild(_) instanceof Generated::TypeExpr + TSuper(QL::SuperRef sup) or + TIdentifier(QL::Variable var) or + TAsExpr(QL::AsExpr asExpr) { asExpr.getChild(1) instanceof QL::VarName } or + TPredicateCall(QL::CallOrUnqualAggExpr call) or + TMemberCall(QL::QualifiedExpr expr) { + not expr.getChild(_).(QL::QualifiedRhs).getChild(_) instanceof QL::TypeExpr } or - TInlineCast(Generated::QualifiedExpr expr) { - expr.getChild(_).(Generated::QualifiedRhs).getChild(_) instanceof Generated::TypeExpr + TInlineCast(QL::QualifiedExpr expr) { + expr.getChild(_).(QL::QualifiedRhs).getChild(_) instanceof QL::TypeExpr } or - TNoneCall(Generated::SpecialCall call) or - TAnyCall(Generated::Aggregate agg) { - "any" = agg.getChild(0).(Generated::AggId).getValue() and - not agg.getChild(_) instanceof Generated::FullAggregateBody + TNoneCall(QL::SpecialCall call) or + TAnyCall(QL::Aggregate agg) { + "any" = agg.getChild(0).(QL::AggId).getValue() and + not agg.getChild(_) instanceof QL::FullAggregateBody } or - TNegation(Generated::Negation neg) or - TIfFormula(Generated::IfTerm ifterm) or - TImplication(Generated::Implication impl) or - TInstanceOf(Generated::InstanceOf inst) or - TInFormula(Generated::InExpr inexpr) or - THigherOrderFormula(Generated::HigherOrderTerm hop) or - TExprAnnotation(Generated::ExprAnnotation expr_anno) or - TAddSubExpr(Generated::AddExpr addexp) or - TMulDivModExpr(Generated::MulExpr mulexpr) or - TRange(Generated::Range range) or - TSet(Generated::SetLiteral set) or - TLiteral(Generated::Literal lit) or - TUnaryExpr(Generated::UnaryExpr unaryexpr) or - TDontCare(Generated::Underscore dontcare) or - TModuleExpr(Generated::ModuleExpr me) or - TPredicateExpr(Generated::PredicateExpr pe) or - TAnnotation(Generated::Annotation annot) or - TAnnotationArg(Generated::AnnotArg arg) or - TYamlCommemt(Generated::YamlComment yc) or - TYamlEntry(Generated::YamlEntry ye) or - TYamlKey(Generated::YamlKey yk) or - TYamlListitem(Generated::YamlListitem yli) or - TYamlValue(Generated::YamlValue yv) or + TNegation(QL::Negation neg) or + TIfFormula(QL::IfTerm ifterm) or + TImplication(QL::Implication impl) or + TInstanceOf(QL::InstanceOf inst) or + TInFormula(QL::InExpr inexpr) or + THigherOrderFormula(QL::HigherOrderTerm hop) or + TExprAnnotation(QL::ExprAnnotation expr_anno) or + TAddSubExpr(QL::AddExpr addexp) or + TMulDivModExpr(QL::MulExpr mulexpr) or + TRange(QL::Range range) or + TSet(QL::SetLiteral set) or + TLiteral(QL::Literal lit) or + TUnaryExpr(QL::UnaryExpr unaryexpr) or + TDontCare(QL::Underscore dontcare) or + TModuleExpr(QL::ModuleExpr me) or + TPredicateExpr(QL::PredicateExpr pe) or + TAnnotation(QL::Annotation annot) or + TAnnotationArg(QL::AnnotArg arg) or + TYamlCommemt(QL::YamlComment yc) or + TYamlEntry(QL::YamlEntry ye) or + TYamlKey(QL::YamlKey yk) or + TYamlListitem(QL::YamlListitem yli) or + TYamlValue(QL::YamlValue yv) or TBuiltinClassless(string ret, string name, string args) { isBuiltinClassless(ret, name, args) } or TBuiltinMember(string qual, string ret, string name, string args) { isBuiltinMember(qual, ret, name, args) @@ -90,7 +90,7 @@ class TModuleRef = TImport or TModuleExpr; class TYAMLNode = TYamlCommemt or TYamlEntry or TYamlKey or TYamlListitem or TYamlValue; -private Generated::AstNode toGeneratedFormula(AST::AstNode n) { +private QL::AstNode toQLFormula(AST::AstNode n) { n = TConjunction(result) or n = TDisjunction(result) or n = TComparisonFormula(result) or @@ -106,7 +106,7 @@ private Generated::AstNode toGeneratedFormula(AST::AstNode n) { n = TInFormula(result) } -private Generated::AstNode toGeneratedExpr(AST::AstNode n) { +private QL::AstNode toQLExpr(AST::AstNode n) { n = TAddSubExpr(result) or n = TMulDivModExpr(result) or n = TRange(result) or @@ -120,7 +120,7 @@ private Generated::AstNode toGeneratedExpr(AST::AstNode n) { n = TDontCare(result) } -private Generated::AstNode toGenerateYAML(AST::AstNode n) { +private QL::AstNode toGenerateYAML(AST::AstNode n) { n = TYamlCommemt(result) or n = TYamlEntry(result) or n = TYamlKey(result) or @@ -131,19 +131,19 @@ private Generated::AstNode toGenerateYAML(AST::AstNode n) { /** * Gets the underlying TreeSitter entity for a given AST node. */ -Generated::AstNode toGenerated(AST::AstNode n) { - result = toGeneratedExpr(n) +QL::AstNode toQL(AST::AstNode n) { + result = toQLExpr(n) or - result = toGeneratedFormula(n) + result = toQLFormula(n) or result = toGenerateYAML(n) or - result.(Generated::ParExpr).getChild() = toGenerated(n) + result.(QL::ParExpr).getChild() = toQL(n) or result = - any(Generated::AsExpr ae | - not ae.getChild(1) instanceof Generated::VarName and - toGenerated(n) = ae.getChild(0) + any(QL::AsExpr ae | + not ae.getChild(1) instanceof QL::VarName and + toQL(n) = ae.getChild(0) ) or n = TTopLevel(result) diff --git a/ql/src/codeql_ql/ast/internal/Module.qll b/ql/src/codeql_ql/ast/internal/Module.qll index a64ccfd0f2d..e5edc0ee875 100644 --- a/ql/src/codeql_ql/ast/internal/Module.qll +++ b/ql/src/codeql_ql/ast/internal/Module.qll @@ -149,13 +149,13 @@ private predicate resolveSelectionName(Import imp, ContainerOrModule m, int i) { cached private module Cached { // TODO: Use `AstNode::getParent` once it is total - private Generated::AstNode parent(Generated::AstNode n) { + private QL::AstNode parent(QL::AstNode n) { result = n.getParent() and - not n instanceof Generated::Module + not n instanceof QL::Module } private Module getEnclosingModule0(AstNode n) { - AstNodes::toGenerated(result) = parent*(AstNodes::toGenerated(n).getParent()) + AstNodes::toQL(result) = parent*(AstNodes::toQL(n).getParent()) } cached diff --git a/ql/src/codeql_ql/ast/internal/TreeSitter.qll b/ql/src/codeql_ql/ast/internal/TreeSitter.qll index 15191b154f2..cfef117f269 100644 --- a/ql/src/codeql_ql/ast/internal/TreeSitter.qll +++ b/ql/src/codeql_ql/ast/internal/TreeSitter.qll @@ -3,12 +3,12 @@ * Automatically generated from the tree-sitter grammar; do not edit */ -module Generated { - private import codeql.files.FileSystem - private import codeql.Locations +private import codeql.files.FileSystem +private import codeql.Locations +module QL { /** The base class for all AST nodes */ - class AstNode extends @ast_node { + class AstNode extends @ql_ast_node { /** Gets a string representation of this element. */ string toString() { result = this.getAPrimaryQlClass() } @@ -16,1759 +16,1764 @@ module Generated { Location getLocation() { none() } /** Gets the parent of this element. */ - AstNode getParent() { ast_node_parent(this, result, _) } + AstNode getParent() { ql_ast_node_parent(this, result, _) } /** Gets the index of this node among the children of its parent. */ - int getParentIndex() { ast_node_parent(this, _, result) } + int getParentIndex() { ql_ast_node_parent(this, _, result) } /** Gets a field or child node of this node. */ AstNode getAFieldOrChild() { none() } /** Gets the name of the primary QL class for this element. */ string getAPrimaryQlClass() { result = "???" } + + /** Gets a comma-separated list of the names of the primary CodeQL classes to which this element belongs. */ + string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") } } /** A token. */ - class Token extends @token, AstNode { + class Token extends @ql_token, AstNode { /** Gets the value of this token. */ - string getValue() { tokeninfo(this, _, _, _, result, _) } + string getValue() { ql_tokeninfo(this, _, result, _) } /** Gets the location of this token. */ - override Location getLocation() { tokeninfo(this, _, _, _, _, result) } + override Location getLocation() { ql_tokeninfo(this, _, _, result) } /** Gets a string representation of this element. */ - override string toString() { result = getValue() } + override string toString() { result = this.getValue() } /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Token" } } /** A reserved word. */ - class ReservedWord extends @reserved_word, Token { + class ReservedWord extends @ql_reserved_word, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ReservedWord" } } /** A class representing `add_expr` nodes. */ - class AddExpr extends @add_expr, AstNode { + class AddExpr extends @ql_add_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AddExpr" } /** Gets the location of this element. */ - override Location getLocation() { add_expr_def(this, _, _, _, result) } + override Location getLocation() { ql_add_expr_def(this, _, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { add_expr_def(this, result, _, _, _) } + AstNode getLeft() { ql_add_expr_def(this, result, _, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { add_expr_def(this, _, result, _, _) } + AstNode getRight() { ql_add_expr_def(this, _, result, _, _) } /** Gets the child of this node. */ - Addop getChild() { add_expr_def(this, _, _, result, _) } + Addop getChild() { ql_add_expr_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - add_expr_def(this, result, _, _, _) or - add_expr_def(this, _, result, _, _) or - add_expr_def(this, _, _, result, _) + ql_add_expr_def(this, result, _, _, _) or + ql_add_expr_def(this, _, result, _, _) or + ql_add_expr_def(this, _, _, result, _) } } /** A class representing `addop` tokens. */ - class Addop extends @token_addop, Token { + class Addop extends @ql_token_addop, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Addop" } } /** A class representing `aggId` tokens. */ - class AggId extends @token_agg_id, Token { + class AggId extends @ql_token_agg_id, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AggId" } } /** A class representing `aggregate` nodes. */ - class Aggregate extends @aggregate, AstNode { + class Aggregate extends @ql_aggregate, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Aggregate" } /** Gets the location of this element. */ - override Location getLocation() { aggregate_def(this, result) } + override Location getLocation() { ql_aggregate_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { aggregate_child(this, i, result) } + AstNode getChild(int i) { ql_aggregate_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { aggregate_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_aggregate_child(this, _, result) } } /** A class representing `annotArg` nodes. */ - class AnnotArg extends @annot_arg, AstNode { + class AnnotArg extends @ql_annot_arg, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AnnotArg" } /** Gets the location of this element. */ - override Location getLocation() { annot_arg_def(this, _, result) } + override Location getLocation() { ql_annot_arg_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { annot_arg_def(this, result, _) } + AstNode getChild() { ql_annot_arg_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { annot_arg_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_annot_arg_def(this, result, _) } } /** A class representing `annotName` tokens. */ - class AnnotName extends @token_annot_name, Token { + class AnnotName extends @ql_token_annot_name, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AnnotName" } } /** A class representing `annotation` nodes. */ - class Annotation extends @annotation, AstNode { + class Annotation extends @ql_annotation, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Annotation" } /** Gets the location of this element. */ - override Location getLocation() { annotation_def(this, _, result) } + override Location getLocation() { ql_annotation_def(this, _, result) } /** Gets the node corresponding to the field `args`. */ - AstNode getArgs(int i) { annotation_args(this, i, result) } + AstNode getArgs(int i) { ql_annotation_args(this, i, result) } /** Gets the node corresponding to the field `name`. */ - AnnotName getName() { annotation_def(this, result, _) } + AnnotName getName() { ql_annotation_def(this, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - annotation_args(this, _, result) or annotation_def(this, result, _) + ql_annotation_args(this, _, result) or ql_annotation_def(this, result, _) } } /** A class representing `aritylessPredicateExpr` nodes. */ - class AritylessPredicateExpr extends @arityless_predicate_expr, AstNode { + class AritylessPredicateExpr extends @ql_arityless_predicate_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AritylessPredicateExpr" } /** Gets the location of this element. */ - override Location getLocation() { arityless_predicate_expr_def(this, _, result) } + override Location getLocation() { ql_arityless_predicate_expr_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - LiteralId getName() { arityless_predicate_expr_def(this, result, _) } + LiteralId getName() { ql_arityless_predicate_expr_def(this, result, _) } /** Gets the child of this node. */ - ModuleExpr getChild() { arityless_predicate_expr_child(this, result) } + ModuleExpr getChild() { ql_arityless_predicate_expr_child(this, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - arityless_predicate_expr_def(this, result, _) or arityless_predicate_expr_child(this, result) + ql_arityless_predicate_expr_def(this, result, _) or + ql_arityless_predicate_expr_child(this, result) } } /** A class representing `asExpr` nodes. */ - class AsExpr extends @as_expr, AstNode { + class AsExpr extends @ql_as_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AsExpr" } /** Gets the location of this element. */ - override Location getLocation() { as_expr_def(this, result) } + override Location getLocation() { ql_as_expr_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { as_expr_child(this, i, result) } + AstNode getChild(int i) { ql_as_expr_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { as_expr_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_as_expr_child(this, _, result) } } /** A class representing `asExprs` nodes. */ - class AsExprs extends @as_exprs, AstNode { + class AsExprs extends @ql_as_exprs, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "AsExprs" } /** Gets the location of this element. */ - override Location getLocation() { as_exprs_def(this, result) } + override Location getLocation() { ql_as_exprs_def(this, result) } /** Gets the `i`th child of this node. */ - AsExpr getChild(int i) { as_exprs_child(this, i, result) } + AsExpr getChild(int i) { ql_as_exprs_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { as_exprs_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_as_exprs_child(this, _, result) } } /** A class representing `block_comment` tokens. */ - class BlockComment extends @token_block_comment, Token { + class BlockComment extends @ql_token_block_comment, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "BlockComment" } } /** A class representing `body` nodes. */ - class Body extends @body, AstNode { + class Body extends @ql_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Body" } /** Gets the location of this element. */ - override Location getLocation() { body_def(this, _, result) } + override Location getLocation() { ql_body_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { body_def(this, result, _) } + AstNode getChild() { ql_body_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { body_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_body_def(this, result, _) } } /** A class representing `bool` nodes. */ - class Bool extends @bool, AstNode { + class Bool extends @ql_bool, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Bool" } /** Gets the location of this element. */ - override Location getLocation() { bool_def(this, _, result) } + override Location getLocation() { ql_bool_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { bool_def(this, result, _) } + AstNode getChild() { ql_bool_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { bool_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_bool_def(this, result, _) } } /** A class representing `call_body` nodes. */ - class CallBody extends @call_body, AstNode { + class CallBody extends @ql_call_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "CallBody" } /** Gets the location of this element. */ - override Location getLocation() { call_body_def(this, result) } + override Location getLocation() { ql_call_body_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { call_body_child(this, i, result) } + AstNode getChild(int i) { ql_call_body_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { call_body_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_call_body_child(this, _, result) } } /** A class representing `call_or_unqual_agg_expr` nodes. */ - class CallOrUnqualAggExpr extends @call_or_unqual_agg_expr, AstNode { + class CallOrUnqualAggExpr extends @ql_call_or_unqual_agg_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "CallOrUnqualAggExpr" } /** Gets the location of this element. */ - override Location getLocation() { call_or_unqual_agg_expr_def(this, result) } + override Location getLocation() { ql_call_or_unqual_agg_expr_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { call_or_unqual_agg_expr_child(this, i, result) } + AstNode getChild(int i) { ql_call_or_unqual_agg_expr_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { call_or_unqual_agg_expr_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_call_or_unqual_agg_expr_child(this, _, result) } } /** A class representing `charpred` nodes. */ - class Charpred extends @charpred, AstNode { + class Charpred extends @ql_charpred, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Charpred" } /** Gets the location of this element. */ - override Location getLocation() { charpred_def(this, _, _, result) } + override Location getLocation() { ql_charpred_def(this, _, _, result) } /** Gets the node corresponding to the field `body`. */ - AstNode getBody() { charpred_def(this, result, _, _) } + AstNode getBody() { ql_charpred_def(this, result, _, _) } /** Gets the child of this node. */ - ClassName getChild() { charpred_def(this, _, result, _) } + ClassName getChild() { ql_charpred_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - charpred_def(this, result, _, _) or charpred_def(this, _, result, _) + ql_charpred_def(this, result, _, _) or ql_charpred_def(this, _, result, _) } } /** A class representing `classMember` nodes. */ - class ClassMember extends @class_member, AstNode { + class ClassMember extends @ql_class_member, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ClassMember" } /** Gets the location of this element. */ - override Location getLocation() { class_member_def(this, result) } + override Location getLocation() { ql_class_member_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { class_member_child(this, i, result) } + AstNode getChild(int i) { ql_class_member_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { class_member_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_class_member_child(this, _, result) } } /** A class representing `className` tokens. */ - class ClassName extends @token_class_name, Token { + class ClassName extends @ql_token_class_name, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ClassName" } } /** A class representing `classlessPredicate` nodes. */ - class ClasslessPredicate extends @classless_predicate, AstNode { + class ClasslessPredicate extends @ql_classless_predicate, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ClasslessPredicate" } /** Gets the location of this element. */ - override Location getLocation() { classless_predicate_def(this, _, _, result) } + override Location getLocation() { ql_classless_predicate_def(this, _, _, result) } /** Gets the node corresponding to the field `name`. */ - PredicateName getName() { classless_predicate_def(this, result, _, _) } + PredicateName getName() { ql_classless_predicate_def(this, result, _, _) } /** Gets the node corresponding to the field `returnType`. */ - AstNode getReturnType() { classless_predicate_def(this, _, result, _) } + AstNode getReturnType() { ql_classless_predicate_def(this, _, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { classless_predicate_child(this, i, result) } + AstNode getChild(int i) { ql_classless_predicate_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - classless_predicate_def(this, result, _, _) or - classless_predicate_def(this, _, result, _) or - classless_predicate_child(this, _, result) + ql_classless_predicate_def(this, result, _, _) or + ql_classless_predicate_def(this, _, result, _) or + ql_classless_predicate_child(this, _, result) } } /** A class representing `closure` tokens. */ - class Closure extends @token_closure, Token { + class Closure extends @ql_token_closure, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Closure" } } /** A class representing `comp_term` nodes. */ - class CompTerm extends @comp_term, AstNode { + class CompTerm extends @ql_comp_term, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "CompTerm" } /** Gets the location of this element. */ - override Location getLocation() { comp_term_def(this, _, _, _, result) } + override Location getLocation() { ql_comp_term_def(this, _, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { comp_term_def(this, result, _, _, _) } + AstNode getLeft() { ql_comp_term_def(this, result, _, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { comp_term_def(this, _, result, _, _) } + AstNode getRight() { ql_comp_term_def(this, _, result, _, _) } /** Gets the child of this node. */ - Compop getChild() { comp_term_def(this, _, _, result, _) } + Compop getChild() { ql_comp_term_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - comp_term_def(this, result, _, _, _) or - comp_term_def(this, _, result, _, _) or - comp_term_def(this, _, _, result, _) + ql_comp_term_def(this, result, _, _, _) or + ql_comp_term_def(this, _, result, _, _) or + ql_comp_term_def(this, _, _, result, _) } } /** A class representing `compop` tokens. */ - class Compop extends @token_compop, Token { + class Compop extends @ql_token_compop, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Compop" } } /** A class representing `conjunction` nodes. */ - class Conjunction extends @conjunction, AstNode { + class Conjunction extends @ql_conjunction, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Conjunction" } /** Gets the location of this element. */ - override Location getLocation() { conjunction_def(this, _, _, result) } + override Location getLocation() { ql_conjunction_def(this, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { conjunction_def(this, result, _, _) } + AstNode getLeft() { ql_conjunction_def(this, result, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { conjunction_def(this, _, result, _) } + AstNode getRight() { ql_conjunction_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - conjunction_def(this, result, _, _) or conjunction_def(this, _, result, _) + ql_conjunction_def(this, result, _, _) or ql_conjunction_def(this, _, result, _) } } /** A class representing `dataclass` nodes. */ - class Dataclass extends @dataclass, AstNode { + class Dataclass extends @ql_dataclass, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Dataclass" } /** Gets the location of this element. */ - override Location getLocation() { dataclass_def(this, _, result) } + override Location getLocation() { ql_dataclass_def(this, _, result) } /** Gets the node corresponding to the field `extends`. */ - AstNode getExtends(int i) { dataclass_extends(this, i, result) } + AstNode getExtends(int i) { ql_dataclass_extends(this, i, result) } /** Gets the node corresponding to the field `instanceof`. */ - AstNode getInstanceof(int i) { dataclass_instanceof(this, i, result) } + AstNode getInstanceof(int i) { ql_dataclass_instanceof(this, i, result) } /** Gets the node corresponding to the field `name`. */ - ClassName getName() { dataclass_def(this, result, _) } + ClassName getName() { ql_dataclass_def(this, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { dataclass_child(this, i, result) } + AstNode getChild(int i) { ql_dataclass_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - dataclass_extends(this, _, result) or - dataclass_instanceof(this, _, result) or - dataclass_def(this, result, _) or - dataclass_child(this, _, result) + ql_dataclass_extends(this, _, result) or + ql_dataclass_instanceof(this, _, result) or + ql_dataclass_def(this, result, _) or + ql_dataclass_child(this, _, result) } } /** A class representing `datatype` nodes. */ - class Datatype extends @datatype, AstNode { + class Datatype extends @ql_datatype, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Datatype" } /** Gets the location of this element. */ - override Location getLocation() { datatype_def(this, _, _, result) } + override Location getLocation() { ql_datatype_def(this, _, _, result) } /** Gets the node corresponding to the field `name`. */ - ClassName getName() { datatype_def(this, result, _, _) } + ClassName getName() { ql_datatype_def(this, result, _, _) } /** Gets the child of this node. */ - DatatypeBranches getChild() { datatype_def(this, _, result, _) } + DatatypeBranches getChild() { ql_datatype_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - datatype_def(this, result, _, _) or datatype_def(this, _, result, _) + ql_datatype_def(this, result, _, _) or ql_datatype_def(this, _, result, _) } } /** A class representing `datatypeBranch` nodes. */ - class DatatypeBranch extends @datatype_branch, AstNode { + class DatatypeBranch extends @ql_datatype_branch, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DatatypeBranch" } /** Gets the location of this element. */ - override Location getLocation() { datatype_branch_def(this, _, result) } + override Location getLocation() { ql_datatype_branch_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - ClassName getName() { datatype_branch_def(this, result, _) } + ClassName getName() { ql_datatype_branch_def(this, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { datatype_branch_child(this, i, result) } + AstNode getChild(int i) { ql_datatype_branch_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - datatype_branch_def(this, result, _) or datatype_branch_child(this, _, result) + ql_datatype_branch_def(this, result, _) or ql_datatype_branch_child(this, _, result) } } /** A class representing `datatypeBranches` nodes. */ - class DatatypeBranches extends @datatype_branches, AstNode { + class DatatypeBranches extends @ql_datatype_branches, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DatatypeBranches" } /** Gets the location of this element. */ - override Location getLocation() { datatype_branches_def(this, result) } + override Location getLocation() { ql_datatype_branches_def(this, result) } /** Gets the `i`th child of this node. */ - DatatypeBranch getChild(int i) { datatype_branches_child(this, i, result) } + DatatypeBranch getChild(int i) { ql_datatype_branches_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { datatype_branches_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_datatype_branches_child(this, _, result) } } /** A class representing `db_annotation` nodes. */ - class DbAnnotation extends @db_annotation, AstNode { + class DbAnnotation extends @ql_db_annotation, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbAnnotation" } /** Gets the location of this element. */ - override Location getLocation() { db_annotation_def(this, result) } + override Location getLocation() { ql_db_annotation_def(this, result) } /** Gets the node corresponding to the field `argsAnnotation`. */ - DbArgsAnnotation getArgsAnnotation() { db_annotation_args_annotation(this, result) } + DbArgsAnnotation getArgsAnnotation() { ql_db_annotation_args_annotation(this, result) } /** Gets the node corresponding to the field `simpleAnnotation`. */ - AnnotName getSimpleAnnotation() { db_annotation_simple_annotation(this, result) } + AnnotName getSimpleAnnotation() { ql_db_annotation_simple_annotation(this, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_annotation_args_annotation(this, result) or db_annotation_simple_annotation(this, result) + ql_db_annotation_args_annotation(this, result) or + ql_db_annotation_simple_annotation(this, result) } } /** A class representing `db_argsAnnotation` nodes. */ - class DbArgsAnnotation extends @db_args_annotation, AstNode { + class DbArgsAnnotation extends @ql_db_args_annotation, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbArgsAnnotation" } /** Gets the location of this element. */ - override Location getLocation() { db_args_annotation_def(this, _, result) } + override Location getLocation() { ql_db_args_annotation_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - AnnotName getName() { db_args_annotation_def(this, result, _) } + AnnotName getName() { ql_db_args_annotation_def(this, result, _) } /** Gets the `i`th child of this node. */ - SimpleId getChild(int i) { db_args_annotation_child(this, i, result) } + SimpleId getChild(int i) { ql_db_args_annotation_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_args_annotation_def(this, result, _) or db_args_annotation_child(this, _, result) + ql_db_args_annotation_def(this, result, _) or ql_db_args_annotation_child(this, _, result) } } /** A class representing `db_boolean` tokens. */ - class DbBoolean extends @token_db_boolean, Token { + class DbBoolean extends @ql_token_db_boolean, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbBoolean" } } /** A class representing `db_branch` nodes. */ - class DbBranch extends @db_branch, AstNode { + class DbBranch extends @ql_db_branch, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbBranch" } /** Gets the location of this element. */ - override Location getLocation() { db_branch_def(this, result) } + override Location getLocation() { ql_db_branch_def(this, result) } /** Gets the node corresponding to the field `qldoc`. */ - Qldoc getQldoc() { db_branch_qldoc(this, result) } + Qldoc getQldoc() { ql_db_branch_qldoc(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { db_branch_child(this, i, result) } + AstNode getChild(int i) { ql_db_branch_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_branch_qldoc(this, result) or db_branch_child(this, _, result) + ql_db_branch_qldoc(this, result) or ql_db_branch_child(this, _, result) } } /** A class representing `db_case` tokens. */ - class DbCase extends @token_db_case, Token { + class DbCase extends @ql_token_db_case, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbCase" } } /** A class representing `db_caseDecl` nodes. */ - class DbCaseDecl extends @db_case_decl, AstNode { + class DbCaseDecl extends @ql_db_case_decl, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbCaseDecl" } /** Gets the location of this element. */ - override Location getLocation() { db_case_decl_def(this, _, _, result) } + override Location getLocation() { ql_db_case_decl_def(this, _, _, result) } /** Gets the node corresponding to the field `base`. */ - Dbtype getBase() { db_case_decl_def(this, result, _, _) } + Dbtype getBase() { ql_db_case_decl_def(this, result, _, _) } /** Gets the node corresponding to the field `discriminator`. */ - SimpleId getDiscriminator() { db_case_decl_def(this, _, result, _) } + SimpleId getDiscriminator() { ql_db_case_decl_def(this, _, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { db_case_decl_child(this, i, result) } + AstNode getChild(int i) { ql_db_case_decl_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_case_decl_def(this, result, _, _) or - db_case_decl_def(this, _, result, _) or - db_case_decl_child(this, _, result) + ql_db_case_decl_def(this, result, _, _) or + ql_db_case_decl_def(this, _, result, _) or + ql_db_case_decl_child(this, _, result) } } /** A class representing `db_colType` nodes. */ - class DbColType extends @db_col_type, AstNode { + class DbColType extends @ql_db_col_type, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbColType" } /** Gets the location of this element. */ - override Location getLocation() { db_col_type_def(this, _, result) } + override Location getLocation() { ql_db_col_type_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { db_col_type_def(this, result, _) } + AstNode getChild() { ql_db_col_type_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { db_col_type_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_db_col_type_def(this, result, _) } } /** A class representing `db_column` nodes. */ - class DbColumn extends @db_column, AstNode { + class DbColumn extends @ql_db_column, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbColumn" } /** Gets the location of this element. */ - override Location getLocation() { db_column_def(this, _, _, _, result) } + override Location getLocation() { ql_db_column_def(this, _, _, _, result) } /** Gets the node corresponding to the field `colName`. */ - SimpleId getColName() { db_column_def(this, result, _, _, _) } + SimpleId getColName() { ql_db_column_def(this, result, _, _, _) } /** Gets the node corresponding to the field `colType`. */ - DbColType getColType() { db_column_def(this, _, result, _, _) } + DbColType getColType() { ql_db_column_def(this, _, result, _, _) } /** Gets the node corresponding to the field `isRef`. */ - DbRef getIsRef() { db_column_is_ref(this, result) } + DbRef getIsRef() { ql_db_column_is_ref(this, result) } /** Gets the node corresponding to the field `isUnique`. */ - DbUnique getIsUnique() { db_column_is_unique(this, result) } + DbUnique getIsUnique() { ql_db_column_is_unique(this, result) } /** Gets the node corresponding to the field `qldoc`. */ - Qldoc getQldoc() { db_column_qldoc(this, result) } + Qldoc getQldoc() { ql_db_column_qldoc(this, result) } /** Gets the node corresponding to the field `reprType`. */ - DbReprType getReprType() { db_column_def(this, _, _, result, _) } + DbReprType getReprType() { ql_db_column_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_column_def(this, result, _, _, _) or - db_column_def(this, _, result, _, _) or - db_column_is_ref(this, result) or - db_column_is_unique(this, result) or - db_column_qldoc(this, result) or - db_column_def(this, _, _, result, _) + ql_db_column_def(this, result, _, _, _) or + ql_db_column_def(this, _, result, _, _) or + ql_db_column_is_ref(this, result) or + ql_db_column_is_unique(this, result) or + ql_db_column_qldoc(this, result) or + ql_db_column_def(this, _, _, result, _) } } /** A class representing `db_date` tokens. */ - class DbDate extends @token_db_date, Token { + class DbDate extends @ql_token_db_date, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbDate" } } /** A class representing `db_entry` nodes. */ - class DbEntry extends @db_entry, AstNode { + class DbEntry extends @ql_db_entry, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbEntry" } /** Gets the location of this element. */ - override Location getLocation() { db_entry_def(this, _, result) } + override Location getLocation() { ql_db_entry_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { db_entry_def(this, result, _) } + AstNode getChild() { ql_db_entry_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { db_entry_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_db_entry_def(this, result, _) } } /** A class representing `db_float` tokens. */ - class DbFloat extends @token_db_float, Token { + class DbFloat extends @ql_token_db_float, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbFloat" } } /** A class representing `db_int` tokens. */ - class DbInt extends @token_db_int, Token { + class DbInt extends @ql_token_db_int, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbInt" } } /** A class representing `db_ref` tokens. */ - class DbRef extends @token_db_ref, Token { + class DbRef extends @ql_token_db_ref, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbRef" } } /** A class representing `db_reprType` nodes. */ - class DbReprType extends @db_repr_type, AstNode { + class DbReprType extends @ql_db_repr_type, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbReprType" } /** Gets the location of this element. */ - override Location getLocation() { db_repr_type_def(this, result) } + override Location getLocation() { ql_db_repr_type_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { db_repr_type_child(this, i, result) } + AstNode getChild(int i) { ql_db_repr_type_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { db_repr_type_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_db_repr_type_child(this, _, result) } } /** A class representing `db_string` tokens. */ - class DbString extends @token_db_string, Token { + class DbString extends @ql_token_db_string, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbString" } } /** A class representing `db_table` nodes. */ - class DbTable extends @db_table, AstNode { + class DbTable extends @ql_db_table, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbTable" } /** Gets the location of this element. */ - override Location getLocation() { db_table_def(this, _, result) } + override Location getLocation() { ql_db_table_def(this, _, result) } /** Gets the node corresponding to the field `tableName`. */ - DbTableName getTableName() { db_table_def(this, result, _) } + DbTableName getTableName() { ql_db_table_def(this, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { db_table_child(this, i, result) } + AstNode getChild(int i) { ql_db_table_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_table_def(this, result, _) or db_table_child(this, _, result) + ql_db_table_def(this, result, _) or ql_db_table_child(this, _, result) } } /** A class representing `db_tableName` nodes. */ - class DbTableName extends @db_table_name, AstNode { + class DbTableName extends @ql_db_table_name, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbTableName" } /** Gets the location of this element. */ - override Location getLocation() { db_table_name_def(this, _, result) } + override Location getLocation() { ql_db_table_name_def(this, _, result) } /** Gets the child of this node. */ - SimpleId getChild() { db_table_name_def(this, result, _) } + SimpleId getChild() { ql_db_table_name_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { db_table_name_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_db_table_name_def(this, result, _) } } /** A class representing `db_unionDecl` nodes. */ - class DbUnionDecl extends @db_union_decl, AstNode { + class DbUnionDecl extends @ql_db_union_decl, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbUnionDecl" } /** Gets the location of this element. */ - override Location getLocation() { db_union_decl_def(this, _, result) } + override Location getLocation() { ql_db_union_decl_def(this, _, result) } /** Gets the node corresponding to the field `base`. */ - Dbtype getBase() { db_union_decl_def(this, result, _) } + Dbtype getBase() { ql_db_union_decl_def(this, result, _) } /** Gets the `i`th child of this node. */ - Dbtype getChild(int i) { db_union_decl_child(this, i, result) } + Dbtype getChild(int i) { ql_db_union_decl_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - db_union_decl_def(this, result, _) or db_union_decl_child(this, _, result) + ql_db_union_decl_def(this, result, _) or ql_db_union_decl_child(this, _, result) } } /** A class representing `db_unique` tokens. */ - class DbUnique extends @token_db_unique, Token { + class DbUnique extends @ql_token_db_unique, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbUnique" } } /** A class representing `db_varchar` tokens. */ - class DbVarchar extends @token_db_varchar, Token { + class DbVarchar extends @ql_token_db_varchar, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "DbVarchar" } } /** A class representing `dbtype` tokens. */ - class Dbtype extends @token_dbtype, Token { + class Dbtype extends @ql_token_dbtype, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Dbtype" } } /** A class representing `direction` tokens. */ - class Direction extends @token_direction, Token { + class Direction extends @ql_token_direction, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Direction" } } /** A class representing `disjunction` nodes. */ - class Disjunction extends @disjunction, AstNode { + class Disjunction extends @ql_disjunction, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Disjunction" } /** Gets the location of this element. */ - override Location getLocation() { disjunction_def(this, _, _, result) } + override Location getLocation() { ql_disjunction_def(this, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { disjunction_def(this, result, _, _) } + AstNode getLeft() { ql_disjunction_def(this, result, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { disjunction_def(this, _, result, _) } + AstNode getRight() { ql_disjunction_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - disjunction_def(this, result, _, _) or disjunction_def(this, _, result, _) + ql_disjunction_def(this, result, _, _) or ql_disjunction_def(this, _, result, _) } } /** A class representing `empty` tokens. */ - class Empty extends @token_empty, Token { + class Empty extends @ql_token_empty, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Empty" } } /** A class representing `expr_aggregate_body` nodes. */ - class ExprAggregateBody extends @expr_aggregate_body, AstNode { + class ExprAggregateBody extends @ql_expr_aggregate_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ExprAggregateBody" } /** Gets the location of this element. */ - override Location getLocation() { expr_aggregate_body_def(this, _, result) } + override Location getLocation() { ql_expr_aggregate_body_def(this, _, result) } /** Gets the node corresponding to the field `asExprs`. */ - AsExprs getAsExprs() { expr_aggregate_body_def(this, result, _) } + AsExprs getAsExprs() { ql_expr_aggregate_body_def(this, result, _) } /** Gets the node corresponding to the field `orderBys`. */ - OrderBys getOrderBys() { expr_aggregate_body_order_bys(this, result) } + OrderBys getOrderBys() { ql_expr_aggregate_body_order_bys(this, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - expr_aggregate_body_def(this, result, _) or expr_aggregate_body_order_bys(this, result) + ql_expr_aggregate_body_def(this, result, _) or ql_expr_aggregate_body_order_bys(this, result) } } /** A class representing `expr_annotation` nodes. */ - class ExprAnnotation extends @expr_annotation, AstNode { + class ExprAnnotation extends @ql_expr_annotation, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ExprAnnotation" } /** Gets the location of this element. */ - override Location getLocation() { expr_annotation_def(this, _, _, _, result) } + override Location getLocation() { ql_expr_annotation_def(this, _, _, _, result) } /** Gets the node corresponding to the field `annot_arg`. */ - AnnotName getAnnotArg() { expr_annotation_def(this, result, _, _, _) } + AnnotName getAnnotArg() { ql_expr_annotation_def(this, result, _, _, _) } /** Gets the node corresponding to the field `name`. */ - AnnotName getName() { expr_annotation_def(this, _, result, _, _) } + AnnotName getName() { ql_expr_annotation_def(this, _, result, _, _) } /** Gets the child of this node. */ - AstNode getChild() { expr_annotation_def(this, _, _, result, _) } + AstNode getChild() { ql_expr_annotation_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - expr_annotation_def(this, result, _, _, _) or - expr_annotation_def(this, _, result, _, _) or - expr_annotation_def(this, _, _, result, _) + ql_expr_annotation_def(this, result, _, _, _) or + ql_expr_annotation_def(this, _, result, _, _) or + ql_expr_annotation_def(this, _, _, result, _) } } /** A class representing `false` tokens. */ - class False extends @token_false, Token { + class False extends @ql_token_false, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "False" } } /** A class representing `field` nodes. */ - class Field extends @field, AstNode { + class Field extends @ql_field, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Field" } /** Gets the location of this element. */ - override Location getLocation() { field_def(this, _, result) } + override Location getLocation() { ql_field_def(this, _, result) } /** Gets the child of this node. */ - VarDecl getChild() { field_def(this, result, _) } + VarDecl getChild() { ql_field_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { field_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_field_def(this, result, _) } } /** A class representing `float` tokens. */ - class Float extends @token_float, Token { + class Float extends @ql_token_float, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Float" } } /** A class representing `full_aggregate_body` nodes. */ - class FullAggregateBody extends @full_aggregate_body, AstNode { + class FullAggregateBody extends @ql_full_aggregate_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "FullAggregateBody" } /** Gets the location of this element. */ - override Location getLocation() { full_aggregate_body_def(this, result) } + override Location getLocation() { ql_full_aggregate_body_def(this, result) } /** Gets the node corresponding to the field `asExprs`. */ - AsExprs getAsExprs() { full_aggregate_body_as_exprs(this, result) } + AsExprs getAsExprs() { ql_full_aggregate_body_as_exprs(this, result) } /** Gets the node corresponding to the field `guard`. */ - AstNode getGuard() { full_aggregate_body_guard(this, result) } + AstNode getGuard() { ql_full_aggregate_body_guard(this, result) } /** Gets the node corresponding to the field `orderBys`. */ - OrderBys getOrderBys() { full_aggregate_body_order_bys(this, result) } + OrderBys getOrderBys() { ql_full_aggregate_body_order_bys(this, result) } /** Gets the `i`th child of this node. */ - VarDecl getChild(int i) { full_aggregate_body_child(this, i, result) } + VarDecl getChild(int i) { ql_full_aggregate_body_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - full_aggregate_body_as_exprs(this, result) or - full_aggregate_body_guard(this, result) or - full_aggregate_body_order_bys(this, result) or - full_aggregate_body_child(this, _, result) + ql_full_aggregate_body_as_exprs(this, result) or + ql_full_aggregate_body_guard(this, result) or + ql_full_aggregate_body_order_bys(this, result) or + ql_full_aggregate_body_child(this, _, result) } } /** A class representing `higherOrderTerm` nodes. */ - class HigherOrderTerm extends @higher_order_term, AstNode { + class HigherOrderTerm extends @ql_higher_order_term, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "HigherOrderTerm" } /** Gets the location of this element. */ - override Location getLocation() { higher_order_term_def(this, _, result) } + override Location getLocation() { ql_higher_order_term_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - LiteralId getName() { higher_order_term_def(this, result, _) } + LiteralId getName() { ql_higher_order_term_def(this, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { higher_order_term_child(this, i, result) } + AstNode getChild(int i) { ql_higher_order_term_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - higher_order_term_def(this, result, _) or higher_order_term_child(this, _, result) + ql_higher_order_term_def(this, result, _) or ql_higher_order_term_child(this, _, result) } } /** A class representing `if_term` nodes. */ - class IfTerm extends @if_term, AstNode { + class IfTerm extends @ql_if_term, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "IfTerm" } /** Gets the location of this element. */ - override Location getLocation() { if_term_def(this, _, _, _, result) } + override Location getLocation() { ql_if_term_def(this, _, _, _, result) } /** Gets the node corresponding to the field `cond`. */ - AstNode getCond() { if_term_def(this, result, _, _, _) } + AstNode getCond() { ql_if_term_def(this, result, _, _, _) } /** Gets the node corresponding to the field `first`. */ - AstNode getFirst() { if_term_def(this, _, result, _, _) } + AstNode getFirst() { ql_if_term_def(this, _, result, _, _) } /** Gets the node corresponding to the field `second`. */ - AstNode getSecond() { if_term_def(this, _, _, result, _) } + AstNode getSecond() { ql_if_term_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - if_term_def(this, result, _, _, _) or - if_term_def(this, _, result, _, _) or - if_term_def(this, _, _, result, _) + ql_if_term_def(this, result, _, _, _) or + ql_if_term_def(this, _, result, _, _) or + ql_if_term_def(this, _, _, result, _) } } /** A class representing `implication` nodes. */ - class Implication extends @implication, AstNode { + class Implication extends @ql_implication, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Implication" } /** Gets the location of this element. */ - override Location getLocation() { implication_def(this, _, _, result) } + override Location getLocation() { ql_implication_def(this, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { implication_def(this, result, _, _) } + AstNode getLeft() { ql_implication_def(this, result, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { implication_def(this, _, result, _) } + AstNode getRight() { ql_implication_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - implication_def(this, result, _, _) or implication_def(this, _, result, _) + ql_implication_def(this, result, _, _) or ql_implication_def(this, _, result, _) } } /** A class representing `importDirective` nodes. */ - class ImportDirective extends @import_directive, AstNode { + class ImportDirective extends @ql_import_directive, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ImportDirective" } /** Gets the location of this element. */ - override Location getLocation() { import_directive_def(this, result) } + override Location getLocation() { ql_import_directive_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { import_directive_child(this, i, result) } + AstNode getChild(int i) { ql_import_directive_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { import_directive_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_import_directive_child(this, _, result) } } /** A class representing `importModuleExpr` nodes. */ - class ImportModuleExpr extends @import_module_expr, AstNode { + class ImportModuleExpr extends @ql_import_module_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ImportModuleExpr" } /** Gets the location of this element. */ - override Location getLocation() { import_module_expr_def(this, _, result) } + override Location getLocation() { ql_import_module_expr_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - SimpleId getName(int i) { import_module_expr_name(this, i, result) } + SimpleId getName(int i) { ql_import_module_expr_name(this, i, result) } /** Gets the child of this node. */ - QualModuleExpr getChild() { import_module_expr_def(this, result, _) } + QualModuleExpr getChild() { ql_import_module_expr_def(this, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - import_module_expr_name(this, _, result) or import_module_expr_def(this, result, _) + ql_import_module_expr_name(this, _, result) or ql_import_module_expr_def(this, result, _) } } /** A class representing `in_expr` nodes. */ - class InExpr extends @in_expr, AstNode { + class InExpr extends @ql_in_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "InExpr" } /** Gets the location of this element. */ - override Location getLocation() { in_expr_def(this, _, _, result) } + override Location getLocation() { ql_in_expr_def(this, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { in_expr_def(this, result, _, _) } + AstNode getLeft() { ql_in_expr_def(this, result, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { in_expr_def(this, _, result, _) } + AstNode getRight() { ql_in_expr_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - in_expr_def(this, result, _, _) or in_expr_def(this, _, result, _) + ql_in_expr_def(this, result, _, _) or ql_in_expr_def(this, _, result, _) } } /** A class representing `instance_of` nodes. */ - class InstanceOf extends @instance_of, AstNode { + class InstanceOf extends @ql_instance_of, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "InstanceOf" } /** Gets the location of this element. */ - override Location getLocation() { instance_of_def(this, result) } + override Location getLocation() { ql_instance_of_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { instance_of_child(this, i, result) } + AstNode getChild(int i) { ql_instance_of_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { instance_of_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_instance_of_child(this, _, result) } } /** A class representing `integer` tokens. */ - class Integer extends @token_integer, Token { + class Integer extends @ql_token_integer, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Integer" } } /** A class representing `line_comment` tokens. */ - class LineComment extends @token_line_comment, Token { + class LineComment extends @ql_token_line_comment, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "LineComment" } } /** A class representing `literal` nodes. */ - class Literal extends @literal, AstNode { + class Literal extends @ql_literal, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Literal" } /** Gets the location of this element. */ - override Location getLocation() { literal_def(this, _, result) } + override Location getLocation() { ql_literal_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { literal_def(this, result, _) } + AstNode getChild() { ql_literal_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { literal_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_literal_def(this, result, _) } } /** A class representing `literalId` tokens. */ - class LiteralId extends @token_literal_id, Token { + class LiteralId extends @ql_token_literal_id, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "LiteralId" } } /** A class representing `memberPredicate` nodes. */ - class MemberPredicate extends @member_predicate, AstNode { + class MemberPredicate extends @ql_member_predicate, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "MemberPredicate" } /** Gets the location of this element. */ - override Location getLocation() { member_predicate_def(this, _, _, result) } + override Location getLocation() { ql_member_predicate_def(this, _, _, result) } /** Gets the node corresponding to the field `name`. */ - PredicateName getName() { member_predicate_def(this, result, _, _) } + PredicateName getName() { ql_member_predicate_def(this, result, _, _) } /** Gets the node corresponding to the field `returnType`. */ - AstNode getReturnType() { member_predicate_def(this, _, result, _) } + AstNode getReturnType() { ql_member_predicate_def(this, _, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { member_predicate_child(this, i, result) } + AstNode getChild(int i) { ql_member_predicate_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - member_predicate_def(this, result, _, _) or - member_predicate_def(this, _, result, _) or - member_predicate_child(this, _, result) + ql_member_predicate_def(this, result, _, _) or + ql_member_predicate_def(this, _, result, _) or + ql_member_predicate_child(this, _, result) } } /** A class representing `module` nodes. */ - class Module extends @module, AstNode { + class Module extends @ql_module, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Module" } /** Gets the location of this element. */ - override Location getLocation() { module_def(this, _, result) } + override Location getLocation() { ql_module_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - ModuleName getName() { module_def(this, result, _) } + ModuleName getName() { ql_module_def(this, result, _) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { module_child(this, i, result) } + AstNode getChild(int i) { ql_module_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - module_def(this, result, _) or module_child(this, _, result) + ql_module_def(this, result, _) or ql_module_child(this, _, result) } } /** A class representing `moduleAliasBody` nodes. */ - class ModuleAliasBody extends @module_alias_body, AstNode { + class ModuleAliasBody extends @ql_module_alias_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ModuleAliasBody" } /** Gets the location of this element. */ - override Location getLocation() { module_alias_body_def(this, _, result) } + override Location getLocation() { ql_module_alias_body_def(this, _, result) } /** Gets the child of this node. */ - ModuleExpr getChild() { module_alias_body_def(this, result, _) } + ModuleExpr getChild() { ql_module_alias_body_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { module_alias_body_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_module_alias_body_def(this, result, _) } } /** A class representing `moduleExpr` nodes. */ - class ModuleExpr extends @module_expr, AstNode { + class ModuleExpr extends @ql_module_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ModuleExpr" } /** Gets the location of this element. */ - override Location getLocation() { module_expr_def(this, _, result) } + override Location getLocation() { ql_module_expr_def(this, _, result) } /** Gets the node corresponding to the field `name`. */ - SimpleId getName() { module_expr_name(this, result) } + SimpleId getName() { ql_module_expr_name(this, result) } /** Gets the child of this node. */ - AstNode getChild() { module_expr_def(this, result, _) } + AstNode getChild() { ql_module_expr_def(this, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - module_expr_name(this, result) or module_expr_def(this, result, _) + ql_module_expr_name(this, result) or ql_module_expr_def(this, result, _) } } /** A class representing `moduleMember` nodes. */ - class ModuleMember extends @module_member, AstNode { + class ModuleMember extends @ql_module_member, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ModuleMember" } /** Gets the location of this element. */ - override Location getLocation() { module_member_def(this, result) } + override Location getLocation() { ql_module_member_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { module_member_child(this, i, result) } + AstNode getChild(int i) { ql_module_member_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { module_member_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_module_member_child(this, _, result) } } /** A class representing `moduleName` nodes. */ - class ModuleName extends @module_name, AstNode { + class ModuleName extends @ql_module_name, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ModuleName" } /** Gets the location of this element. */ - override Location getLocation() { module_name_def(this, _, result) } + override Location getLocation() { ql_module_name_def(this, _, result) } /** Gets the child of this node. */ - SimpleId getChild() { module_name_def(this, result, _) } + SimpleId getChild() { ql_module_name_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { module_name_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_module_name_def(this, result, _) } } /** A class representing `mul_expr` nodes. */ - class MulExpr extends @mul_expr, AstNode { + class MulExpr extends @ql_mul_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "MulExpr" } /** Gets the location of this element. */ - override Location getLocation() { mul_expr_def(this, _, _, _, result) } + override Location getLocation() { ql_mul_expr_def(this, _, _, _, result) } /** Gets the node corresponding to the field `left`. */ - AstNode getLeft() { mul_expr_def(this, result, _, _, _) } + AstNode getLeft() { ql_mul_expr_def(this, result, _, _, _) } /** Gets the node corresponding to the field `right`. */ - AstNode getRight() { mul_expr_def(this, _, result, _, _) } + AstNode getRight() { ql_mul_expr_def(this, _, result, _, _) } /** Gets the child of this node. */ - Mulop getChild() { mul_expr_def(this, _, _, result, _) } + Mulop getChild() { ql_mul_expr_def(this, _, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - mul_expr_def(this, result, _, _, _) or - mul_expr_def(this, _, result, _, _) or - mul_expr_def(this, _, _, result, _) + ql_mul_expr_def(this, result, _, _, _) or + ql_mul_expr_def(this, _, result, _, _) or + ql_mul_expr_def(this, _, _, result, _) } } /** A class representing `mulop` tokens. */ - class Mulop extends @token_mulop, Token { + class Mulop extends @ql_token_mulop, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Mulop" } } /** A class representing `negation` nodes. */ - class Negation extends @negation, AstNode { + class Negation extends @ql_negation, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Negation" } /** Gets the location of this element. */ - override Location getLocation() { negation_def(this, _, result) } + override Location getLocation() { ql_negation_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { negation_def(this, result, _) } + AstNode getChild() { ql_negation_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { negation_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_negation_def(this, result, _) } } /** A class representing `orderBy` nodes. */ - class OrderBy extends @order_by, AstNode { + class OrderBy extends @ql_order_by, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "OrderBy" } /** Gets the location of this element. */ - override Location getLocation() { order_by_def(this, result) } + override Location getLocation() { ql_order_by_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { order_by_child(this, i, result) } + AstNode getChild(int i) { ql_order_by_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { order_by_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_order_by_child(this, _, result) } } /** A class representing `orderBys` nodes. */ - class OrderBys extends @order_bys, AstNode { + class OrderBys extends @ql_order_bys, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "OrderBys" } /** Gets the location of this element. */ - override Location getLocation() { order_bys_def(this, result) } + override Location getLocation() { ql_order_bys_def(this, result) } /** Gets the `i`th child of this node. */ - OrderBy getChild(int i) { order_bys_child(this, i, result) } + OrderBy getChild(int i) { ql_order_bys_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { order_bys_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_order_bys_child(this, _, result) } } /** A class representing `par_expr` nodes. */ - class ParExpr extends @par_expr, AstNode { + class ParExpr extends @ql_par_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "ParExpr" } /** Gets the location of this element. */ - override Location getLocation() { par_expr_def(this, _, result) } + override Location getLocation() { ql_par_expr_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { par_expr_def(this, result, _) } + AstNode getChild() { ql_par_expr_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { par_expr_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_par_expr_def(this, result, _) } } /** A class representing `predicate` tokens. */ - class Predicate extends @token_predicate, Token { + class Predicate extends @ql_token_predicate, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Predicate" } } /** A class representing `predicateAliasBody` nodes. */ - class PredicateAliasBody extends @predicate_alias_body, AstNode { + class PredicateAliasBody extends @ql_predicate_alias_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "PredicateAliasBody" } /** Gets the location of this element. */ - override Location getLocation() { predicate_alias_body_def(this, _, result) } + override Location getLocation() { ql_predicate_alias_body_def(this, _, result) } /** Gets the child of this node. */ - PredicateExpr getChild() { predicate_alias_body_def(this, result, _) } + PredicateExpr getChild() { ql_predicate_alias_body_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { predicate_alias_body_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_predicate_alias_body_def(this, result, _) } } /** A class representing `predicateExpr` nodes. */ - class PredicateExpr extends @predicate_expr, AstNode { + class PredicateExpr extends @ql_predicate_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "PredicateExpr" } /** Gets the location of this element. */ - override Location getLocation() { predicate_expr_def(this, result) } + override Location getLocation() { ql_predicate_expr_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { predicate_expr_child(this, i, result) } + AstNode getChild(int i) { ql_predicate_expr_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { predicate_expr_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_predicate_expr_child(this, _, result) } } /** A class representing `predicateName` tokens. */ - class PredicateName extends @token_predicate_name, Token { + class PredicateName extends @ql_token_predicate_name, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "PredicateName" } } /** A class representing `prefix_cast` nodes. */ - class PrefixCast extends @prefix_cast, AstNode { + class PrefixCast extends @ql_prefix_cast, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "PrefixCast" } /** Gets the location of this element. */ - override Location getLocation() { prefix_cast_def(this, result) } + override Location getLocation() { ql_prefix_cast_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { prefix_cast_child(this, i, result) } + AstNode getChild(int i) { ql_prefix_cast_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { prefix_cast_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_prefix_cast_child(this, _, result) } } /** A class representing `primitiveType` tokens. */ - class PrimitiveType extends @token_primitive_type, Token { + class PrimitiveType extends @ql_token_primitive_type, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "PrimitiveType" } } /** A class representing `ql` nodes. */ - class Ql extends @ql, AstNode { + class Ql extends @ql_ql, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Ql" } /** Gets the location of this element. */ - override Location getLocation() { ql_def(this, result) } + override Location getLocation() { ql_ql_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { ql_child(this, i, result) } + AstNode getChild(int i) { ql_ql_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { ql_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_ql_child(this, _, result) } } /** A class representing `qldoc` tokens. */ - class Qldoc extends @token_qldoc, Token { + class Qldoc extends @ql_token_qldoc, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Qldoc" } } /** A class representing `qualModuleExpr` nodes. */ - class QualModuleExpr extends @qual_module_expr, AstNode { + class QualModuleExpr extends @ql_qual_module_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "QualModuleExpr" } /** Gets the location of this element. */ - override Location getLocation() { qual_module_expr_def(this, result) } + override Location getLocation() { ql_qual_module_expr_def(this, result) } /** Gets the node corresponding to the field `name`. */ - SimpleId getName(int i) { qual_module_expr_name(this, i, result) } + SimpleId getName(int i) { ql_qual_module_expr_name(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { qual_module_expr_name(this, _, result) } + override AstNode getAFieldOrChild() { ql_qual_module_expr_name(this, _, result) } } /** A class representing `qualifiedRhs` nodes. */ - class QualifiedRhs extends @qualified_rhs, AstNode { + class QualifiedRhs extends @ql_qualified_rhs, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "QualifiedRhs" } /** Gets the location of this element. */ - override Location getLocation() { qualified_rhs_def(this, result) } + override Location getLocation() { ql_qualified_rhs_def(this, result) } /** Gets the node corresponding to the field `name`. */ - PredicateName getName() { qualified_rhs_name(this, result) } + PredicateName getName() { ql_qualified_rhs_name(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { qualified_rhs_child(this, i, result) } + AstNode getChild(int i) { ql_qualified_rhs_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - qualified_rhs_name(this, result) or qualified_rhs_child(this, _, result) + ql_qualified_rhs_name(this, result) or ql_qualified_rhs_child(this, _, result) } } /** A class representing `qualified_expr` nodes. */ - class QualifiedExpr extends @qualified_expr, AstNode { + class QualifiedExpr extends @ql_qualified_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "QualifiedExpr" } /** Gets the location of this element. */ - override Location getLocation() { qualified_expr_def(this, result) } + override Location getLocation() { ql_qualified_expr_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { qualified_expr_child(this, i, result) } + AstNode getChild(int i) { ql_qualified_expr_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { qualified_expr_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_qualified_expr_child(this, _, result) } } /** A class representing `quantified` nodes. */ - class Quantified extends @quantified, AstNode { + class Quantified extends @ql_quantified, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Quantified" } /** Gets the location of this element. */ - override Location getLocation() { quantified_def(this, result) } + override Location getLocation() { ql_quantified_def(this, result) } /** Gets the node corresponding to the field `expr`. */ - AstNode getExpr() { quantified_expr(this, result) } + AstNode getExpr() { ql_quantified_expr(this, result) } /** Gets the node corresponding to the field `formula`. */ - AstNode getFormula() { quantified_formula(this, result) } + AstNode getFormula() { ql_quantified_formula(this, result) } /** Gets the node corresponding to the field `range`. */ - AstNode getRange() { quantified_range(this, result) } + AstNode getRange() { ql_quantified_range(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { quantified_child(this, i, result) } + AstNode getChild(int i) { ql_quantified_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - quantified_expr(this, result) or - quantified_formula(this, result) or - quantified_range(this, result) or - quantified_child(this, _, result) + ql_quantified_expr(this, result) or + ql_quantified_formula(this, result) or + ql_quantified_range(this, result) or + ql_quantified_child(this, _, result) } } /** A class representing `quantifier` tokens. */ - class Quantifier extends @token_quantifier, Token { + class Quantifier extends @ql_token_quantifier, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Quantifier" } } /** A class representing `range` nodes. */ - class Range extends @range, AstNode { + class Range extends @ql_range, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Range" } /** Gets the location of this element. */ - override Location getLocation() { range_def(this, _, _, result) } + override Location getLocation() { ql_range_def(this, _, _, result) } /** Gets the node corresponding to the field `lower`. */ - AstNode getLower() { range_def(this, result, _, _) } + AstNode getLower() { ql_range_def(this, result, _, _) } /** Gets the node corresponding to the field `upper`. */ - AstNode getUpper() { range_def(this, _, result, _) } + AstNode getUpper() { ql_range_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - range_def(this, result, _, _) or range_def(this, _, result, _) + ql_range_def(this, result, _, _) or ql_range_def(this, _, result, _) } } /** A class representing `result` tokens. */ - class Result extends @token_result, Token { + class Result extends @ql_token_result, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Result" } } /** A class representing `select` nodes. */ - class Select extends @select, AstNode { + class Select extends @ql_select, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Select" } /** Gets the location of this element. */ - override Location getLocation() { select_def(this, result) } + override Location getLocation() { ql_select_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { select_child(this, i, result) } + AstNode getChild(int i) { ql_select_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { select_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_select_child(this, _, result) } } /** A class representing `set_literal` nodes. */ - class SetLiteral extends @set_literal, AstNode { + class SetLiteral extends @ql_set_literal, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "SetLiteral" } /** Gets the location of this element. */ - override Location getLocation() { set_literal_def(this, result) } + override Location getLocation() { ql_set_literal_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { set_literal_child(this, i, result) } + AstNode getChild(int i) { ql_set_literal_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { set_literal_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_set_literal_child(this, _, result) } } /** A class representing `simpleId` tokens. */ - class SimpleId extends @token_simple_id, Token { + class SimpleId extends @ql_token_simple_id, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "SimpleId" } } /** A class representing `specialId` tokens. */ - class SpecialId extends @token_special_id, Token { + class SpecialId extends @ql_token_special_id, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "SpecialId" } } /** A class representing `special_call` nodes. */ - class SpecialCall extends @special_call, AstNode { + class SpecialCall extends @ql_special_call, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "SpecialCall" } /** Gets the location of this element. */ - override Location getLocation() { special_call_def(this, _, result) } + override Location getLocation() { ql_special_call_def(this, _, result) } /** Gets the child of this node. */ - SpecialId getChild() { special_call_def(this, result, _) } + SpecialId getChild() { ql_special_call_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { special_call_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_special_call_def(this, result, _) } } /** A class representing `string` tokens. */ - class String extends @token_string, Token { + class String extends @ql_token_string, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "String" } } /** A class representing `super` tokens. */ - class Super extends @token_super, Token { + class Super extends @ql_token_super, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Super" } } /** A class representing `super_ref` nodes. */ - class SuperRef extends @super_ref, AstNode { + class SuperRef extends @ql_super_ref, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "SuperRef" } /** Gets the location of this element. */ - override Location getLocation() { super_ref_def(this, result) } + override Location getLocation() { ql_super_ref_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { super_ref_child(this, i, result) } + AstNode getChild(int i) { ql_super_ref_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { super_ref_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_super_ref_child(this, _, result) } } /** A class representing `this` tokens. */ - class This extends @token_this, Token { + class This extends @ql_token_this, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "This" } } /** A class representing `true` tokens. */ - class True extends @token_true, Token { + class True extends @ql_token_true, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "True" } } /** A class representing `typeAliasBody` nodes. */ - class TypeAliasBody extends @type_alias_body, AstNode { + class TypeAliasBody extends @ql_type_alias_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "TypeAliasBody" } /** Gets the location of this element. */ - override Location getLocation() { type_alias_body_def(this, _, result) } + override Location getLocation() { ql_type_alias_body_def(this, _, result) } /** Gets the child of this node. */ - TypeExpr getChild() { type_alias_body_def(this, result, _) } + TypeExpr getChild() { ql_type_alias_body_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { type_alias_body_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_type_alias_body_def(this, result, _) } } /** A class representing `typeExpr` nodes. */ - class TypeExpr extends @type_expr, AstNode { + class TypeExpr extends @ql_type_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "TypeExpr" } /** Gets the location of this element. */ - override Location getLocation() { type_expr_def(this, result) } + override Location getLocation() { ql_type_expr_def(this, result) } /** Gets the node corresponding to the field `name`. */ - ClassName getName() { type_expr_name(this, result) } + ClassName getName() { ql_type_expr_name(this, result) } /** Gets the child of this node. */ - AstNode getChild() { type_expr_child(this, result) } + AstNode getChild() { ql_type_expr_child(this, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - type_expr_name(this, result) or type_expr_child(this, result) + ql_type_expr_name(this, result) or ql_type_expr_child(this, result) } } /** A class representing `typeUnionBody` nodes. */ - class TypeUnionBody extends @type_union_body, AstNode { + class TypeUnionBody extends @ql_type_union_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "TypeUnionBody" } /** Gets the location of this element. */ - override Location getLocation() { type_union_body_def(this, result) } + override Location getLocation() { ql_type_union_body_def(this, result) } /** Gets the `i`th child of this node. */ - TypeExpr getChild(int i) { type_union_body_child(this, i, result) } + TypeExpr getChild(int i) { ql_type_union_body_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { type_union_body_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_type_union_body_child(this, _, result) } } /** A class representing `unary_expr` nodes. */ - class UnaryExpr extends @unary_expr, AstNode { + class UnaryExpr extends @ql_unary_expr, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "UnaryExpr" } /** Gets the location of this element. */ - override Location getLocation() { unary_expr_def(this, result) } + override Location getLocation() { ql_unary_expr_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { unary_expr_child(this, i, result) } + AstNode getChild(int i) { ql_unary_expr_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { unary_expr_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_unary_expr_child(this, _, result) } } /** A class representing `underscore` tokens. */ - class Underscore extends @token_underscore, Token { + class Underscore extends @ql_token_underscore, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Underscore" } } /** A class representing `unop` tokens. */ - class Unop extends @token_unop, Token { + class Unop extends @ql_token_unop, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Unop" } } /** A class representing `unqual_agg_body` nodes. */ - class UnqualAggBody extends @unqual_agg_body, AstNode { + class UnqualAggBody extends @ql_unqual_agg_body, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "UnqualAggBody" } /** Gets the location of this element. */ - override Location getLocation() { unqual_agg_body_def(this, result) } + override Location getLocation() { ql_unqual_agg_body_def(this, result) } /** Gets the node corresponding to the field `asExprs`. */ - AstNode getAsExprs(int i) { unqual_agg_body_as_exprs(this, i, result) } + AstNode getAsExprs(int i) { ql_unqual_agg_body_as_exprs(this, i, result) } /** Gets the node corresponding to the field `guard`. */ - AstNode getGuard() { unqual_agg_body_guard(this, result) } + AstNode getGuard() { ql_unqual_agg_body_guard(this, result) } /** Gets the `i`th child of this node. */ - VarDecl getChild(int i) { unqual_agg_body_child(this, i, result) } + VarDecl getChild(int i) { ql_unqual_agg_body_child(this, i, result) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - unqual_agg_body_as_exprs(this, _, result) or - unqual_agg_body_guard(this, result) or - unqual_agg_body_child(this, _, result) + ql_unqual_agg_body_as_exprs(this, _, result) or + ql_unqual_agg_body_guard(this, result) or + ql_unqual_agg_body_child(this, _, result) } } /** A class representing `varDecl` nodes. */ - class VarDecl extends @var_decl, AstNode { + class VarDecl extends @ql_var_decl, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "VarDecl" } /** Gets the location of this element. */ - override Location getLocation() { var_decl_def(this, result) } + override Location getLocation() { ql_var_decl_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { var_decl_child(this, i, result) } + AstNode getChild(int i) { ql_var_decl_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { var_decl_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_var_decl_child(this, _, result) } } /** A class representing `varName` nodes. */ - class VarName extends @var_name, AstNode { + class VarName extends @ql_var_name, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "VarName" } /** Gets the location of this element. */ - override Location getLocation() { var_name_def(this, _, result) } + override Location getLocation() { ql_var_name_def(this, _, result) } /** Gets the child of this node. */ - SimpleId getChild() { var_name_def(this, result, _) } + SimpleId getChild() { ql_var_name_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { var_name_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_var_name_def(this, result, _) } } /** A class representing `variable` nodes. */ - class Variable extends @variable, AstNode { + class Variable extends @ql_variable, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "Variable" } /** Gets the location of this element. */ - override Location getLocation() { variable_def(this, _, result) } + override Location getLocation() { ql_variable_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { variable_def(this, result, _) } + AstNode getChild() { ql_variable_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { variable_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_variable_def(this, result, _) } } /** A class representing `yaml_comment` nodes. */ - class YamlComment extends @yaml_comment, AstNode { + class YamlComment extends @ql_yaml_comment, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlComment" } /** Gets the location of this element. */ - override Location getLocation() { yaml_comment_def(this, _, result) } + override Location getLocation() { ql_yaml_comment_def(this, _, result) } /** Gets the child of this node. */ - YamlValue getChild() { yaml_comment_def(this, result, _) } + YamlValue getChild() { ql_yaml_comment_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { yaml_comment_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_yaml_comment_def(this, result, _) } } /** A class representing `yaml_entry` nodes. */ - class YamlEntry extends @yaml_entry, AstNode { + class YamlEntry extends @ql_yaml_entry, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlEntry" } /** Gets the location of this element. */ - override Location getLocation() { yaml_entry_def(this, _, result) } + override Location getLocation() { ql_yaml_entry_def(this, _, result) } /** Gets the child of this node. */ - AstNode getChild() { yaml_entry_def(this, result, _) } + AstNode getChild() { ql_yaml_entry_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { yaml_entry_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_yaml_entry_def(this, result, _) } } /** A class representing `yaml_key` nodes. */ - class YamlKey extends @yaml_key, AstNode { + class YamlKey extends @ql_yaml_key, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlKey" } /** Gets the location of this element. */ - override Location getLocation() { yaml_key_def(this, result) } + override Location getLocation() { ql_yaml_key_def(this, result) } /** Gets the `i`th child of this node. */ - AstNode getChild(int i) { yaml_key_child(this, i, result) } + AstNode getChild(int i) { ql_yaml_key_child(this, i, result) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { yaml_key_child(this, _, result) } + override AstNode getAFieldOrChild() { ql_yaml_key_child(this, _, result) } } /** A class representing `yaml_keyvaluepair` nodes. */ - class YamlKeyvaluepair extends @yaml_keyvaluepair, AstNode { + class YamlKeyvaluepair extends @ql_yaml_keyvaluepair, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlKeyvaluepair" } /** Gets the location of this element. */ - override Location getLocation() { yaml_keyvaluepair_def(this, _, _, result) } + override Location getLocation() { ql_yaml_keyvaluepair_def(this, _, _, result) } /** Gets the node corresponding to the field `key`. */ - YamlKey getKey() { yaml_keyvaluepair_def(this, result, _, _) } + YamlKey getKey() { ql_yaml_keyvaluepair_def(this, result, _, _) } /** Gets the node corresponding to the field `value`. */ - YamlValue getValue() { yaml_keyvaluepair_def(this, _, result, _) } + YamlValue getValue() { ql_yaml_keyvaluepair_def(this, _, result, _) } /** Gets a field or child node of this node. */ override AstNode getAFieldOrChild() { - yaml_keyvaluepair_def(this, result, _, _) or yaml_keyvaluepair_def(this, _, result, _) + ql_yaml_keyvaluepair_def(this, result, _, _) or ql_yaml_keyvaluepair_def(this, _, result, _) } } /** A class representing `yaml_listitem` nodes. */ - class YamlListitem extends @yaml_listitem, AstNode { + class YamlListitem extends @ql_yaml_listitem, AstNode { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlListitem" } /** Gets the location of this element. */ - override Location getLocation() { yaml_listitem_def(this, _, result) } + override Location getLocation() { ql_yaml_listitem_def(this, _, result) } /** Gets the child of this node. */ - YamlValue getChild() { yaml_listitem_def(this, result, _) } + YamlValue getChild() { ql_yaml_listitem_def(this, result, _) } /** Gets a field or child node of this node. */ - override AstNode getAFieldOrChild() { yaml_listitem_def(this, result, _) } + override AstNode getAFieldOrChild() { ql_yaml_listitem_def(this, result, _) } } /** A class representing `yaml_value` tokens. */ - class YamlValue extends @token_yaml_value, Token { + class YamlValue extends @ql_token_yaml_value, Token { /** Gets the name of the primary QL class for this element. */ override string getAPrimaryQlClass() { result = "YamlValue" } } diff --git a/ql/src/codeql_ql/ast/internal/Variable.qll b/ql/src/codeql_ql/ast/internal/Variable.qll index 349bad6a7ca..634c4b1a3e8 100644 --- a/ql/src/codeql_ql/ast/internal/Variable.qll +++ b/ql/src/codeql_ql/ast/internal/Variable.qll @@ -60,9 +60,9 @@ pragma[nomagic] VariableScope scopeOf(AstNode n) { result = parent*(n.getParent()) } private string getName(Identifier i) { - exists(Generated::Variable v | + exists(QL::Variable v | i = TIdentifier(v) and - result = v.getChild().(Generated::VarName).getChild().getValue() + result = v.getChild().(QL::VarName).getChild().getValue() ) } diff --git a/ql/src/ql.dbscheme b/ql/src/ql.dbscheme index 36e57f03df4..dbdd64930a3 100644 --- a/ql/src/ql.dbscheme +++ b/ql/src/ql.dbscheme @@ -12,27 +12,14 @@ locations_default( int end_column: int ref ); -@sourceline = @file - -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 + string name: string ref ); folders( unique int id: @folder, - string name: string ref, - string simple: string ref + string name: string ref ); @container = @file | @folder @@ -46,1097 +33,6 @@ sourceLocationPrefix( string prefix: string ref ); -@add_expr_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@add_expr_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -add_expr_def( - unique int id: @add_expr, - int left: @add_expr_left_type ref, - int right: @add_expr_right_type ref, - int child: @token_addop ref, - int loc: @location ref -); - -@aggregate_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_aggregate_body | @expr_annotation | @full_aggregate_body | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_agg_id | @unary_expr | @variable - -#keyset[aggregate, index] -aggregate_child( - int aggregate: @aggregate ref, - int index: int ref, - unique int child: @aggregate_child_type ref -); - -aggregate_def( - unique int id: @aggregate, - int loc: @location ref -); - -@annotArg_child_type = @token_result | @token_simple_id | @token_this - -annot_arg_def( - unique int id: @annot_arg, - int child: @annotArg_child_type ref, - int loc: @location ref -); - -@annotation_args_type = @annot_arg | @reserved_word - -#keyset[annotation, index] -annotation_args( - int annotation: @annotation ref, - int index: int ref, - unique int args: @annotation_args_type ref -); - -annotation_def( - unique int id: @annotation, - int name: @token_annot_name ref, - int loc: @location ref -); - -arityless_predicate_expr_child( - unique int arityless_predicate_expr: @arityless_predicate_expr ref, - unique int child: @module_expr ref -); - -arityless_predicate_expr_def( - unique int id: @arityless_predicate_expr, - int name: @token_literal_id ref, - int loc: @location ref -); - -@asExpr_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @var_name | @variable - -#keyset[as_expr, index] -as_expr_child( - int as_expr: @as_expr ref, - int index: int ref, - unique int child: @asExpr_child_type ref -); - -as_expr_def( - unique int id: @as_expr, - int loc: @location ref -); - -#keyset[as_exprs, index] -as_exprs_child( - int as_exprs: @as_exprs ref, - int index: int ref, - unique int child: @as_expr ref -); - -as_exprs_def( - unique int id: @as_exprs, - int loc: @location ref -); - -@body_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -body_def( - unique int id: @body, - int child: @body_child_type ref, - int loc: @location ref -); - -@bool_child_type = @token_false | @token_true - -bool_def( - unique int id: @bool, - int child: @bool_child_type ref, - int loc: @location ref -); - -@call_body_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_underscore | @unary_expr | @variable - -#keyset[call_body, index] -call_body_child( - int call_body: @call_body ref, - int index: int ref, - unique int child: @call_body_child_type ref -); - -call_body_def( - unique int id: @call_body, - int loc: @location ref -); - -@call_or_unqual_agg_expr_child_type = @arityless_predicate_expr | @call_body | @token_closure | @unqual_agg_body - -#keyset[call_or_unqual_agg_expr, index] -call_or_unqual_agg_expr_child( - int call_or_unqual_agg_expr: @call_or_unqual_agg_expr ref, - int index: int ref, - unique int child: @call_or_unqual_agg_expr_child_type ref -); - -call_or_unqual_agg_expr_def( - unique int id: @call_or_unqual_agg_expr, - int loc: @location ref -); - -@charpred_body_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -charpred_def( - unique int id: @charpred, - int body: @charpred_body_type ref, - int child: @token_class_name ref, - int loc: @location ref -); - -@classMember_child_type = @annotation | @charpred | @field | @member_predicate | @token_qldoc - -#keyset[class_member, index] -class_member_child( - int class_member: @class_member ref, - int index: int ref, - unique int child: @classMember_child_type ref -); - -class_member_def( - unique int id: @class_member, - int loc: @location ref -); - -@classlessPredicate_returnType_type = @token_predicate | @type_expr - -@classlessPredicate_child_type = @body | @higher_order_term | @predicate_alias_body | @token_empty | @var_decl - -#keyset[classless_predicate, index] -classless_predicate_child( - int classless_predicate: @classless_predicate ref, - int index: int ref, - unique int child: @classlessPredicate_child_type ref -); - -classless_predicate_def( - unique int id: @classless_predicate, - int name: @token_predicate_name ref, - int return_type: @classlessPredicate_returnType_type ref, - int loc: @location ref -); - -@comp_term_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@comp_term_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -comp_term_def( - unique int id: @comp_term, - int left: @comp_term_left_type ref, - int right: @comp_term_right_type ref, - int child: @token_compop ref, - int loc: @location ref -); - -@conjunction_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@conjunction_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -conjunction_def( - unique int id: @conjunction, - int left: @conjunction_left_type ref, - int right: @conjunction_right_type ref, - int loc: @location ref -); - -@dataclass_extends_type = @reserved_word | @type_expr - -#keyset[dataclass, index] -dataclass_extends( - int dataclass: @dataclass ref, - int index: int ref, - unique int extends: @dataclass_extends_type ref -); - -@dataclass_instanceof_type = @reserved_word | @type_expr - -#keyset[dataclass, index] -dataclass_instanceof( - int dataclass: @dataclass ref, - int index: int ref, - unique int instanceof: @dataclass_instanceof_type ref -); - -@dataclass_child_type = @class_member | @type_alias_body | @type_union_body - -#keyset[dataclass, index] -dataclass_child( - int dataclass: @dataclass ref, - int index: int ref, - unique int child: @dataclass_child_type ref -); - -dataclass_def( - unique int id: @dataclass, - int name: @token_class_name ref, - int loc: @location ref -); - -datatype_def( - unique int id: @datatype, - int name: @token_class_name ref, - int child: @datatype_branches ref, - int loc: @location ref -); - -@datatypeBranch_child_type = @annotation | @body | @token_qldoc | @var_decl - -#keyset[datatype_branch, index] -datatype_branch_child( - int datatype_branch: @datatype_branch ref, - int index: int ref, - unique int child: @datatypeBranch_child_type ref -); - -datatype_branch_def( - unique int id: @datatype_branch, - int name: @token_class_name ref, - int loc: @location ref -); - -#keyset[datatype_branches, index] -datatype_branches_child( - int datatype_branches: @datatype_branches ref, - int index: int ref, - unique int child: @datatype_branch ref -); - -datatype_branches_def( - unique int id: @datatype_branches, - int loc: @location ref -); - -db_annotation_args_annotation( - unique int db_annotation: @db_annotation ref, - unique int args_annotation: @db_args_annotation ref -); - -db_annotation_simple_annotation( - unique int db_annotation: @db_annotation ref, - unique int simple_annotation: @token_annot_name ref -); - -db_annotation_def( - unique int id: @db_annotation, - int loc: @location ref -); - -#keyset[db_args_annotation, index] -db_args_annotation_child( - int db_args_annotation: @db_args_annotation ref, - int index: int ref, - unique int child: @token_simple_id ref -); - -db_args_annotation_def( - unique int id: @db_args_annotation, - int name: @token_annot_name ref, - int loc: @location ref -); - -db_branch_qldoc( - unique int db_branch: @db_branch ref, - unique int qldoc: @token_qldoc ref -); - -@db_branch_child_type = @token_dbtype | @token_integer - -#keyset[db_branch, index] -db_branch_child( - int db_branch: @db_branch ref, - int index: int ref, - unique int child: @db_branch_child_type ref -); - -db_branch_def( - unique int id: @db_branch, - int loc: @location ref -); - -@db_caseDecl_child_type = @db_branch | @token_db_case - -#keyset[db_case_decl, index] -db_case_decl_child( - int db_case_decl: @db_case_decl ref, - int index: int ref, - unique int child: @db_caseDecl_child_type ref -); - -db_case_decl_def( - unique int id: @db_case_decl, - int base: @token_dbtype ref, - int discriminator: @token_simple_id ref, - int loc: @location ref -); - -@db_colType_child_type = @token_db_boolean | @token_db_date | @token_db_float | @token_db_int | @token_db_string | @token_dbtype - -db_col_type_def( - unique int id: @db_col_type, - int child: @db_colType_child_type ref, - int loc: @location ref -); - -db_column_is_ref( - unique int db_column: @db_column ref, - unique int is_ref: @token_db_ref ref -); - -db_column_is_unique( - unique int db_column: @db_column ref, - unique int is_unique: @token_db_unique ref -); - -db_column_qldoc( - unique int db_column: @db_column ref, - unique int qldoc: @token_qldoc ref -); - -db_column_def( - unique int id: @db_column, - int col_name: @token_simple_id ref, - int col_type: @db_col_type ref, - int repr_type: @db_repr_type ref, - int loc: @location ref -); - -@db_entry_child_type = @db_case_decl | @db_table | @db_union_decl | @token_qldoc - -db_entry_def( - unique int id: @db_entry, - int child: @db_entry_child_type ref, - int loc: @location ref -); - -@db_reprType_child_type = @token_db_boolean | @token_db_date | @token_db_float | @token_db_int | @token_db_string | @token_db_varchar | @token_integer - -#keyset[db_repr_type, index] -db_repr_type_child( - int db_repr_type: @db_repr_type ref, - int index: int ref, - unique int child: @db_reprType_child_type ref -); - -db_repr_type_def( - unique int id: @db_repr_type, - int loc: @location ref -); - -@db_table_child_type = @db_annotation | @db_column - -#keyset[db_table, index] -db_table_child( - int db_table: @db_table ref, - int index: int ref, - unique int child: @db_table_child_type ref -); - -db_table_def( - unique int id: @db_table, - int table_name: @db_table_name ref, - int loc: @location ref -); - -db_table_name_def( - unique int id: @db_table_name, - int child: @token_simple_id ref, - int loc: @location ref -); - -#keyset[db_union_decl, index] -db_union_decl_child( - int db_union_decl: @db_union_decl ref, - int index: int ref, - unique int child: @token_dbtype ref -); - -db_union_decl_def( - unique int id: @db_union_decl, - int base: @token_dbtype ref, - int loc: @location ref -); - -@disjunction_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@disjunction_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -disjunction_def( - unique int id: @disjunction, - int left: @disjunction_left_type ref, - int right: @disjunction_right_type ref, - int loc: @location ref -); - -expr_aggregate_body_order_bys( - unique int expr_aggregate_body: @expr_aggregate_body ref, - unique int order_bys: @order_bys ref -); - -expr_aggregate_body_def( - unique int id: @expr_aggregate_body, - int as_exprs: @as_exprs ref, - int loc: @location ref -); - -@expr_annotation_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -expr_annotation_def( - unique int id: @expr_annotation, - int annot_arg: @token_annot_name ref, - int name: @token_annot_name ref, - int child: @expr_annotation_child_type ref, - int loc: @location ref -); - -field_def( - unique int id: @field, - int child: @var_decl ref, - int loc: @location ref -); - -full_aggregate_body_as_exprs( - unique int full_aggregate_body: @full_aggregate_body ref, - unique int as_exprs: @as_exprs ref -); - -@full_aggregate_body_guard_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -full_aggregate_body_guard( - unique int full_aggregate_body: @full_aggregate_body ref, - unique int guard: @full_aggregate_body_guard_type ref -); - -full_aggregate_body_order_bys( - unique int full_aggregate_body: @full_aggregate_body ref, - unique int order_bys: @order_bys ref -); - -#keyset[full_aggregate_body, index] -full_aggregate_body_child( - int full_aggregate_body: @full_aggregate_body ref, - int index: int ref, - unique int child: @var_decl ref -); - -full_aggregate_body_def( - unique int id: @full_aggregate_body, - int loc: @location ref -); - -@higherOrderTerm_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @predicate_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_underscore | @unary_expr | @variable - -#keyset[higher_order_term, index] -higher_order_term_child( - int higher_order_term: @higher_order_term ref, - int index: int ref, - unique int child: @higherOrderTerm_child_type ref -); - -higher_order_term_def( - unique int id: @higher_order_term, - int name: @token_literal_id ref, - int loc: @location ref -); - -@if_term_cond_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@if_term_first_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@if_term_second_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -if_term_def( - unique int id: @if_term, - int cond: @if_term_cond_type ref, - int first: @if_term_first_type ref, - int second: @if_term_second_type ref, - int loc: @location ref -); - -@implication_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@implication_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -implication_def( - unique int id: @implication, - int left: @implication_left_type ref, - int right: @implication_right_type ref, - int loc: @location ref -); - -@importDirective_child_type = @import_module_expr | @module_name - -#keyset[import_directive, index] -import_directive_child( - int import_directive: @import_directive ref, - int index: int ref, - unique int child: @importDirective_child_type ref -); - -import_directive_def( - unique int id: @import_directive, - int loc: @location ref -); - -#keyset[import_module_expr, index] -import_module_expr_name( - int import_module_expr: @import_module_expr ref, - int index: int ref, - unique int name: @token_simple_id ref -); - -import_module_expr_def( - unique int id: @import_module_expr, - int child: @qual_module_expr ref, - int loc: @location ref -); - -@in_expr_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@in_expr_right_type = @aggregate | @call_or_unqual_agg_expr | @expr_annotation | @literal | @par_expr | @qualified_expr | @range | @set_literal | @super_ref | @variable - -in_expr_def( - unique int id: @in_expr, - int left: @in_expr_left_type ref, - int right: @in_expr_right_type ref, - int loc: @location ref -); - -@instance_of_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @type_expr | @unary_expr | @variable - -#keyset[instance_of, index] -instance_of_child( - int instance_of: @instance_of ref, - int index: int ref, - unique int child: @instance_of_child_type ref -); - -instance_of_def( - unique int id: @instance_of, - int loc: @location ref -); - -@literal_child_type = @bool | @token_float | @token_integer | @token_string - -literal_def( - unique int id: @literal, - int child: @literal_child_type ref, - int loc: @location ref -); - -@memberPredicate_returnType_type = @token_predicate | @type_expr - -@memberPredicate_child_type = @body | @higher_order_term | @token_empty | @var_decl - -#keyset[member_predicate, index] -member_predicate_child( - int member_predicate: @member_predicate ref, - int index: int ref, - unique int child: @memberPredicate_child_type ref -); - -member_predicate_def( - unique int id: @member_predicate, - int name: @token_predicate_name ref, - int return_type: @memberPredicate_returnType_type ref, - int loc: @location ref -); - -@module_child_type = @module_alias_body | @module_member - -#keyset[module, index] -module_child( - int module: @module ref, - int index: int ref, - unique int child: @module_child_type ref -); - -module_def( - unique int id: @module, - int name: @module_name ref, - int loc: @location ref -); - -module_alias_body_def( - unique int id: @module_alias_body, - int child: @module_expr ref, - int loc: @location ref -); - -module_expr_name( - unique int module_expr: @module_expr ref, - unique int name: @token_simple_id ref -); - -@moduleExpr_child_type = @module_expr | @token_simple_id - -module_expr_def( - unique int id: @module_expr, - int child: @moduleExpr_child_type ref, - int loc: @location ref -); - -@moduleMember_child_type = @annotation | @classless_predicate | @dataclass | @datatype | @import_directive | @module | @select | @token_qldoc - -#keyset[module_member, index] -module_member_child( - int module_member: @module_member ref, - int index: int ref, - unique int child: @moduleMember_child_type ref -); - -module_member_def( - unique int id: @module_member, - int loc: @location ref -); - -module_name_def( - unique int id: @module_name, - int child: @token_simple_id ref, - int loc: @location ref -); - -@mul_expr_left_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@mul_expr_right_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -mul_expr_def( - unique int id: @mul_expr, - int left: @mul_expr_left_type ref, - int right: @mul_expr_right_type ref, - int child: @token_mulop ref, - int loc: @location ref -); - -@negation_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -negation_def( - unique int id: @negation, - int child: @negation_child_type ref, - int loc: @location ref -); - -@orderBy_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_direction | @unary_expr | @variable - -#keyset[order_by, index] -order_by_child( - int order_by: @order_by ref, - int index: int ref, - unique int child: @orderBy_child_type ref -); - -order_by_def( - unique int id: @order_by, - int loc: @location ref -); - -#keyset[order_bys, index] -order_bys_child( - int order_bys: @order_bys ref, - int index: int ref, - unique int child: @order_by ref -); - -order_bys_def( - unique int id: @order_bys, - int loc: @location ref -); - -@par_expr_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -par_expr_def( - unique int id: @par_expr, - int child: @par_expr_child_type ref, - int loc: @location ref -); - -predicate_alias_body_def( - unique int id: @predicate_alias_body, - int child: @predicate_expr ref, - int loc: @location ref -); - -@predicateExpr_child_type = @arityless_predicate_expr | @token_integer - -#keyset[predicate_expr, index] -predicate_expr_child( - int predicate_expr: @predicate_expr ref, - int index: int ref, - unique int child: @predicateExpr_child_type ref -); - -predicate_expr_def( - unique int id: @predicate_expr, - int loc: @location ref -); - -@prefix_cast_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @type_expr | @unary_expr | @variable - -#keyset[prefix_cast, index] -prefix_cast_child( - int prefix_cast: @prefix_cast ref, - int index: int ref, - unique int child: @prefix_cast_child_type ref -); - -prefix_cast_def( - unique int id: @prefix_cast, - int loc: @location ref -); - -@ql_child_type = @db_entry | @module_member | @yaml_entry - -#keyset[ql, index] -ql_child( - int ql: @ql ref, - int index: int ref, - unique int child: @ql_child_type ref -); - -ql_def( - unique int id: @ql, - int loc: @location ref -); - -#keyset[qual_module_expr, index] -qual_module_expr_name( - int qual_module_expr: @qual_module_expr ref, - int index: int ref, - unique int name: @token_simple_id ref -); - -qual_module_expr_def( - unique int id: @qual_module_expr, - int loc: @location ref -); - -qualified_rhs_name( - unique int qualified_rhs: @qualified_rhs ref, - unique int name: @token_predicate_name ref -); - -@qualifiedRhs_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_closure | @token_underscore | @type_expr | @unary_expr | @variable - -#keyset[qualified_rhs, index] -qualified_rhs_child( - int qualified_rhs: @qualified_rhs ref, - int index: int ref, - unique int child: @qualifiedRhs_child_type ref -); - -qualified_rhs_def( - unique int id: @qualified_rhs, - int loc: @location ref -); - -@qualified_expr_child_type = @aggregate | @call_or_unqual_agg_expr | @expr_annotation | @literal | @par_expr | @qualified_expr | @qualified_rhs | @range | @set_literal | @super_ref | @variable - -#keyset[qualified_expr, index] -qualified_expr_child( - int qualified_expr: @qualified_expr ref, - int index: int ref, - unique int child: @qualified_expr_child_type ref -); - -qualified_expr_def( - unique int id: @qualified_expr, - int loc: @location ref -); - -@quantified_expr_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -quantified_expr( - unique int quantified: @quantified ref, - unique int expr: @quantified_expr_type ref -); - -@quantified_formula_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -quantified_formula( - unique int quantified: @quantified ref, - unique int formula: @quantified_formula_type ref -); - -@quantified_range_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -quantified_range( - unique int quantified: @quantified ref, - unique int range: @quantified_range_type ref -); - -@quantified_child_type = @token_quantifier | @var_decl - -#keyset[quantified, index] -quantified_child( - int quantified: @quantified ref, - int index: int ref, - unique int child: @quantified_child_type ref -); - -quantified_def( - unique int id: @quantified, - int loc: @location ref -); - -@range_lower_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -@range_upper_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -range_def( - unique int id: @range, - int lower: @range_lower_type ref, - int upper: @range_upper_type ref, - int loc: @location ref -); - -@select_child_type = @add_expr | @aggregate | @as_exprs | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @order_bys | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @var_decl | @variable - -#keyset[select, index] -select_child( - int select: @select ref, - int index: int ref, - unique int child: @select_child_type ref -); - -select_def( - unique int id: @select, - int loc: @location ref -); - -@set_literal_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -#keyset[set_literal, index] -set_literal_child( - int set_literal: @set_literal ref, - int index: int ref, - unique int child: @set_literal_child_type ref -); - -set_literal_def( - unique int id: @set_literal, - int loc: @location ref -); - -special_call_def( - unique int id: @special_call, - int child: @token_special_id ref, - int loc: @location ref -); - -@super_ref_child_type = @token_super | @type_expr - -#keyset[super_ref, index] -super_ref_child( - int super_ref: @super_ref ref, - int index: int ref, - unique int child: @super_ref_child_type ref -); - -super_ref_def( - unique int id: @super_ref, - int loc: @location ref -); - -type_alias_body_def( - unique int id: @type_alias_body, - int child: @type_expr ref, - int loc: @location ref -); - -type_expr_name( - unique int type_expr: @type_expr ref, - unique int name: @token_class_name ref -); - -@typeExpr_child_type = @module_expr | @token_dbtype | @token_primitive_type - -type_expr_child( - unique int type_expr: @type_expr ref, - unique int child: @typeExpr_child_type ref -); - -type_expr_def( - unique int id: @type_expr, - int loc: @location ref -); - -#keyset[type_union_body, index] -type_union_body_child( - int type_union_body: @type_union_body ref, - int index: int ref, - unique int child: @type_expr ref -); - -type_union_body_def( - unique int id: @type_union_body, - int loc: @location ref -); - -@unary_expr_child_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @token_unop | @unary_expr | @variable - -#keyset[unary_expr, index] -unary_expr_child( - int unary_expr: @unary_expr ref, - int index: int ref, - unique int child: @unary_expr_child_type ref -); - -unary_expr_def( - unique int id: @unary_expr, - int loc: @location ref -); - -@unqual_agg_body_asExprs_type = @as_exprs | @reserved_word - -#keyset[unqual_agg_body, index] -unqual_agg_body_as_exprs( - int unqual_agg_body: @unqual_agg_body ref, - int index: int ref, - unique int as_exprs: @unqual_agg_body_asExprs_type ref -); - -@unqual_agg_body_guard_type = @add_expr | @aggregate | @call_or_unqual_agg_expr | @comp_term | @conjunction | @disjunction | @expr_annotation | @if_term | @implication | @in_expr | @instance_of | @literal | @mul_expr | @negation | @par_expr | @prefix_cast | @qualified_expr | @quantified | @range | @set_literal | @special_call | @super_ref | @unary_expr | @variable - -unqual_agg_body_guard( - unique int unqual_agg_body: @unqual_agg_body ref, - unique int guard: @unqual_agg_body_guard_type ref -); - -#keyset[unqual_agg_body, index] -unqual_agg_body_child( - int unqual_agg_body: @unqual_agg_body ref, - int index: int ref, - unique int child: @var_decl ref -); - -unqual_agg_body_def( - unique int id: @unqual_agg_body, - int loc: @location ref -); - -@varDecl_child_type = @type_expr | @var_name - -#keyset[var_decl, index] -var_decl_child( - int var_decl: @var_decl ref, - int index: int ref, - unique int child: @varDecl_child_type ref -); - -var_decl_def( - unique int id: @var_decl, - int loc: @location ref -); - -var_name_def( - unique int id: @var_name, - int child: @token_simple_id ref, - int loc: @location ref -); - -@variable_child_type = @token_result | @token_this | @var_name - -variable_def( - unique int id: @variable, - int child: @variable_child_type ref, - int loc: @location ref -); - -yaml_comment_def( - unique int id: @yaml_comment, - int child: @token_yaml_value ref, - int loc: @location ref -); - -@yaml_entry_child_type = @yaml_comment | @yaml_keyvaluepair | @yaml_listitem - -yaml_entry_def( - unique int id: @yaml_entry, - int child: @yaml_entry_child_type ref, - int loc: @location ref -); - -@yaml_key_child_type = @token_simple_id | @yaml_key - -#keyset[yaml_key, index] -yaml_key_child( - int yaml_key: @yaml_key ref, - int index: int ref, - unique int child: @yaml_key_child_type ref -); - -yaml_key_def( - unique int id: @yaml_key, - int loc: @location ref -); - -yaml_keyvaluepair_def( - unique int id: @yaml_keyvaluepair, - int key__: @yaml_key ref, - int value: @token_yaml_value ref, - int loc: @location ref -); - -yaml_listitem_def( - unique int id: @yaml_listitem, - int child: @token_yaml_value ref, - int loc: @location ref -); - -tokeninfo( - unique int id: @token, - int kind: int ref, - int file: @file ref, - int idx: int ref, - string value: string ref, - int loc: @location ref -); - -case @token.kind of - 0 = @reserved_word -| 1 = @token_addop -| 2 = @token_agg_id -| 3 = @token_annot_name -| 4 = @token_block_comment -| 5 = @token_class_name -| 6 = @token_closure -| 7 = @token_compop -| 8 = @token_db_boolean -| 9 = @token_db_case -| 10 = @token_db_date -| 11 = @token_db_float -| 12 = @token_db_int -| 13 = @token_db_ref -| 14 = @token_db_string -| 15 = @token_db_unique -| 16 = @token_db_varchar -| 17 = @token_dbtype -| 18 = @token_direction -| 19 = @token_empty -| 20 = @token_false -| 21 = @token_float -| 22 = @token_integer -| 23 = @token_line_comment -| 24 = @token_literal_id -| 25 = @token_mulop -| 26 = @token_predicate -| 27 = @token_predicate_name -| 28 = @token_primitive_type -| 29 = @token_qldoc -| 30 = @token_quantifier -| 31 = @token_result -| 32 = @token_simple_id -| 33 = @token_special_id -| 34 = @token_string -| 35 = @token_super -| 36 = @token_this -| 37 = @token_true -| 38 = @token_underscore -| 39 = @token_unop -| 40 = @token_yaml_value -; - - diagnostics( unique int id: @diagnostic, int severity: int ref, @@ -1154,14 +50,1103 @@ case @diagnostic.severity of ; -@ast_node = @add_expr | @aggregate | @annot_arg | @annotation | @arityless_predicate_expr | @as_expr | @as_exprs | @body | @bool | @call_body | @call_or_unqual_agg_expr | @charpred | @class_member | @classless_predicate | @comp_term | @conjunction | @dataclass | @datatype | @datatype_branch | @datatype_branches | @db_annotation | @db_args_annotation | @db_branch | @db_case_decl | @db_col_type | @db_column | @db_entry | @db_repr_type | @db_table | @db_table_name | @db_union_decl | @disjunction | @expr_aggregate_body | @expr_annotation | @field | @full_aggregate_body | @higher_order_term | @if_term | @implication | @import_directive | @import_module_expr | @in_expr | @instance_of | @literal | @member_predicate | @module | @module_alias_body | @module_expr | @module_member | @module_name | @mul_expr | @negation | @order_by | @order_bys | @par_expr | @predicate_alias_body | @predicate_expr | @prefix_cast | @ql | @qual_module_expr | @qualified_expr | @qualified_rhs | @quantified | @range | @select | @set_literal | @special_call | @super_ref | @token | @type_alias_body | @type_expr | @type_union_body | @unary_expr | @unqual_agg_body | @var_decl | @var_name | @variable | @yaml_comment | @yaml_entry | @yaml_key | @yaml_keyvaluepair | @yaml_listitem +@ql_add_expr_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable -@ast_node_parent = @ast_node | @file +@ql_add_expr_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_add_expr_def( + unique int id: @ql_add_expr, + int left: @ql_add_expr_left_type ref, + int right: @ql_add_expr_right_type ref, + int child: @ql_token_addop ref, + int loc: @location ref +); + +@ql_aggregate_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_aggregate_body | @ql_expr_annotation | @ql_full_aggregate_body | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_agg_id | @ql_unary_expr | @ql_variable + +#keyset[ql_aggregate, index] +ql_aggregate_child( + int ql_aggregate: @ql_aggregate ref, + int index: int ref, + unique int child: @ql_aggregate_child_type ref +); + +ql_aggregate_def( + unique int id: @ql_aggregate, + int loc: @location ref +); + +@ql_annotArg_child_type = @ql_token_result | @ql_token_simple_id | @ql_token_this + +ql_annot_arg_def( + unique int id: @ql_annot_arg, + int child: @ql_annotArg_child_type ref, + int loc: @location ref +); + +@ql_annotation_args_type = @ql_annot_arg | @ql_reserved_word + +#keyset[ql_annotation, index] +ql_annotation_args( + int ql_annotation: @ql_annotation ref, + int index: int ref, + unique int args: @ql_annotation_args_type ref +); + +ql_annotation_def( + unique int id: @ql_annotation, + int name: @ql_token_annot_name ref, + int loc: @location ref +); + +ql_arityless_predicate_expr_child( + unique int ql_arityless_predicate_expr: @ql_arityless_predicate_expr ref, + unique int child: @ql_module_expr ref +); + +ql_arityless_predicate_expr_def( + unique int id: @ql_arityless_predicate_expr, + int name: @ql_token_literal_id ref, + int loc: @location ref +); + +@ql_asExpr_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_var_name | @ql_variable + +#keyset[ql_as_expr, index] +ql_as_expr_child( + int ql_as_expr: @ql_as_expr ref, + int index: int ref, + unique int child: @ql_asExpr_child_type ref +); + +ql_as_expr_def( + unique int id: @ql_as_expr, + int loc: @location ref +); + +#keyset[ql_as_exprs, index] +ql_as_exprs_child( + int ql_as_exprs: @ql_as_exprs ref, + int index: int ref, + unique int child: @ql_as_expr ref +); + +ql_as_exprs_def( + unique int id: @ql_as_exprs, + int loc: @location ref +); + +@ql_body_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_body_def( + unique int id: @ql_body, + int child: @ql_body_child_type ref, + int loc: @location ref +); + +@ql_bool_child_type = @ql_token_false | @ql_token_true + +ql_bool_def( + unique int id: @ql_bool, + int child: @ql_bool_child_type ref, + int loc: @location ref +); + +@ql_call_body_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_underscore | @ql_unary_expr | @ql_variable + +#keyset[ql_call_body, index] +ql_call_body_child( + int ql_call_body: @ql_call_body ref, + int index: int ref, + unique int child: @ql_call_body_child_type ref +); + +ql_call_body_def( + unique int id: @ql_call_body, + int loc: @location ref +); + +@ql_call_or_unqual_agg_expr_child_type = @ql_arityless_predicate_expr | @ql_call_body | @ql_token_closure | @ql_unqual_agg_body + +#keyset[ql_call_or_unqual_agg_expr, index] +ql_call_or_unqual_agg_expr_child( + int ql_call_or_unqual_agg_expr: @ql_call_or_unqual_agg_expr ref, + int index: int ref, + unique int child: @ql_call_or_unqual_agg_expr_child_type ref +); + +ql_call_or_unqual_agg_expr_def( + unique int id: @ql_call_or_unqual_agg_expr, + int loc: @location ref +); + +@ql_charpred_body_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_charpred_def( + unique int id: @ql_charpred, + int body: @ql_charpred_body_type ref, + int child: @ql_token_class_name ref, + int loc: @location ref +); + +@ql_classMember_child_type = @ql_annotation | @ql_charpred | @ql_field | @ql_member_predicate | @ql_token_qldoc + +#keyset[ql_class_member, index] +ql_class_member_child( + int ql_class_member: @ql_class_member ref, + int index: int ref, + unique int child: @ql_classMember_child_type ref +); + +ql_class_member_def( + unique int id: @ql_class_member, + int loc: @location ref +); + +@ql_classlessPredicate_returnType_type = @ql_token_predicate | @ql_type_expr + +@ql_classlessPredicate_child_type = @ql_body | @ql_higher_order_term | @ql_predicate_alias_body | @ql_token_empty | @ql_var_decl + +#keyset[ql_classless_predicate, index] +ql_classless_predicate_child( + int ql_classless_predicate: @ql_classless_predicate ref, + int index: int ref, + unique int child: @ql_classlessPredicate_child_type ref +); + +ql_classless_predicate_def( + unique int id: @ql_classless_predicate, + int name: @ql_token_predicate_name ref, + int return_type: @ql_classlessPredicate_returnType_type ref, + int loc: @location ref +); + +@ql_comp_term_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_comp_term_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_comp_term_def( + unique int id: @ql_comp_term, + int left: @ql_comp_term_left_type ref, + int right: @ql_comp_term_right_type ref, + int child: @ql_token_compop ref, + int loc: @location ref +); + +@ql_conjunction_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_conjunction_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_conjunction_def( + unique int id: @ql_conjunction, + int left: @ql_conjunction_left_type ref, + int right: @ql_conjunction_right_type ref, + int loc: @location ref +); + +@ql_dataclass_extends_type = @ql_reserved_word | @ql_type_expr + +#keyset[ql_dataclass, index] +ql_dataclass_extends( + int ql_dataclass: @ql_dataclass ref, + int index: int ref, + unique int extends: @ql_dataclass_extends_type ref +); + +@ql_dataclass_instanceof_type = @ql_reserved_word | @ql_type_expr + +#keyset[ql_dataclass, index] +ql_dataclass_instanceof( + int ql_dataclass: @ql_dataclass ref, + int index: int ref, + unique int instanceof: @ql_dataclass_instanceof_type ref +); + +@ql_dataclass_child_type = @ql_class_member | @ql_type_alias_body | @ql_type_union_body + +#keyset[ql_dataclass, index] +ql_dataclass_child( + int ql_dataclass: @ql_dataclass ref, + int index: int ref, + unique int child: @ql_dataclass_child_type ref +); + +ql_dataclass_def( + unique int id: @ql_dataclass, + int name: @ql_token_class_name ref, + int loc: @location ref +); + +ql_datatype_def( + unique int id: @ql_datatype, + int name: @ql_token_class_name ref, + int child: @ql_datatype_branches ref, + int loc: @location ref +); + +@ql_datatypeBranch_child_type = @ql_annotation | @ql_body | @ql_token_qldoc | @ql_var_decl + +#keyset[ql_datatype_branch, index] +ql_datatype_branch_child( + int ql_datatype_branch: @ql_datatype_branch ref, + int index: int ref, + unique int child: @ql_datatypeBranch_child_type ref +); + +ql_datatype_branch_def( + unique int id: @ql_datatype_branch, + int name: @ql_token_class_name ref, + int loc: @location ref +); + +#keyset[ql_datatype_branches, index] +ql_datatype_branches_child( + int ql_datatype_branches: @ql_datatype_branches ref, + int index: int ref, + unique int child: @ql_datatype_branch ref +); + +ql_datatype_branches_def( + unique int id: @ql_datatype_branches, + int loc: @location ref +); + +ql_db_annotation_args_annotation( + unique int ql_db_annotation: @ql_db_annotation ref, + unique int args_annotation: @ql_db_args_annotation ref +); + +ql_db_annotation_simple_annotation( + unique int ql_db_annotation: @ql_db_annotation ref, + unique int simple_annotation: @ql_token_annot_name ref +); + +ql_db_annotation_def( + unique int id: @ql_db_annotation, + int loc: @location ref +); + +#keyset[ql_db_args_annotation, index] +ql_db_args_annotation_child( + int ql_db_args_annotation: @ql_db_args_annotation ref, + int index: int ref, + unique int child: @ql_token_simple_id ref +); + +ql_db_args_annotation_def( + unique int id: @ql_db_args_annotation, + int name: @ql_token_annot_name ref, + int loc: @location ref +); + +ql_db_branch_qldoc( + unique int ql_db_branch: @ql_db_branch ref, + unique int qldoc: @ql_token_qldoc ref +); + +@ql_db_branch_child_type = @ql_token_dbtype | @ql_token_integer + +#keyset[ql_db_branch, index] +ql_db_branch_child( + int ql_db_branch: @ql_db_branch ref, + int index: int ref, + unique int child: @ql_db_branch_child_type ref +); + +ql_db_branch_def( + unique int id: @ql_db_branch, + int loc: @location ref +); + +@ql_db_caseDecl_child_type = @ql_db_branch | @ql_token_db_case + +#keyset[ql_db_case_decl, index] +ql_db_case_decl_child( + int ql_db_case_decl: @ql_db_case_decl ref, + int index: int ref, + unique int child: @ql_db_caseDecl_child_type ref +); + +ql_db_case_decl_def( + unique int id: @ql_db_case_decl, + int base: @ql_token_dbtype ref, + int discriminator: @ql_token_simple_id ref, + int loc: @location ref +); + +@ql_db_colType_child_type = @ql_token_db_boolean | @ql_token_db_date | @ql_token_db_float | @ql_token_db_int | @ql_token_db_string | @ql_token_dbtype + +ql_db_col_type_def( + unique int id: @ql_db_col_type, + int child: @ql_db_colType_child_type ref, + int loc: @location ref +); + +ql_db_column_is_ref( + unique int ql_db_column: @ql_db_column ref, + unique int is_ref: @ql_token_db_ref ref +); + +ql_db_column_is_unique( + unique int ql_db_column: @ql_db_column ref, + unique int is_unique: @ql_token_db_unique ref +); + +ql_db_column_qldoc( + unique int ql_db_column: @ql_db_column ref, + unique int qldoc: @ql_token_qldoc ref +); + +ql_db_column_def( + unique int id: @ql_db_column, + int col_name: @ql_token_simple_id ref, + int col_type: @ql_db_col_type ref, + int repr_type: @ql_db_repr_type ref, + int loc: @location ref +); + +@ql_db_entry_child_type = @ql_db_case_decl | @ql_db_table | @ql_db_union_decl | @ql_token_qldoc + +ql_db_entry_def( + unique int id: @ql_db_entry, + int child: @ql_db_entry_child_type ref, + int loc: @location ref +); + +@ql_db_reprType_child_type = @ql_token_db_boolean | @ql_token_db_date | @ql_token_db_float | @ql_token_db_int | @ql_token_db_string | @ql_token_db_varchar | @ql_token_integer + +#keyset[ql_db_repr_type, index] +ql_db_repr_type_child( + int ql_db_repr_type: @ql_db_repr_type ref, + int index: int ref, + unique int child: @ql_db_reprType_child_type ref +); + +ql_db_repr_type_def( + unique int id: @ql_db_repr_type, + int loc: @location ref +); + +@ql_db_table_child_type = @ql_db_annotation | @ql_db_column + +#keyset[ql_db_table, index] +ql_db_table_child( + int ql_db_table: @ql_db_table ref, + int index: int ref, + unique int child: @ql_db_table_child_type ref +); + +ql_db_table_def( + unique int id: @ql_db_table, + int table_name: @ql_db_table_name ref, + int loc: @location ref +); + +ql_db_table_name_def( + unique int id: @ql_db_table_name, + int child: @ql_token_simple_id ref, + int loc: @location ref +); + +#keyset[ql_db_union_decl, index] +ql_db_union_decl_child( + int ql_db_union_decl: @ql_db_union_decl ref, + int index: int ref, + unique int child: @ql_token_dbtype ref +); + +ql_db_union_decl_def( + unique int id: @ql_db_union_decl, + int base: @ql_token_dbtype ref, + int loc: @location ref +); + +@ql_disjunction_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_disjunction_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_disjunction_def( + unique int id: @ql_disjunction, + int left: @ql_disjunction_left_type ref, + int right: @ql_disjunction_right_type ref, + int loc: @location ref +); + +ql_expr_aggregate_body_order_bys( + unique int ql_expr_aggregate_body: @ql_expr_aggregate_body ref, + unique int order_bys: @ql_order_bys ref +); + +ql_expr_aggregate_body_def( + unique int id: @ql_expr_aggregate_body, + int as_exprs: @ql_as_exprs ref, + int loc: @location ref +); + +@ql_expr_annotation_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_expr_annotation_def( + unique int id: @ql_expr_annotation, + int annot_arg: @ql_token_annot_name ref, + int name: @ql_token_annot_name ref, + int child: @ql_expr_annotation_child_type ref, + int loc: @location ref +); + +ql_field_def( + unique int id: @ql_field, + int child: @ql_var_decl ref, + int loc: @location ref +); + +ql_full_aggregate_body_as_exprs( + unique int ql_full_aggregate_body: @ql_full_aggregate_body ref, + unique int as_exprs: @ql_as_exprs ref +); + +@ql_full_aggregate_body_guard_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_full_aggregate_body_guard( + unique int ql_full_aggregate_body: @ql_full_aggregate_body ref, + unique int guard: @ql_full_aggregate_body_guard_type ref +); + +ql_full_aggregate_body_order_bys( + unique int ql_full_aggregate_body: @ql_full_aggregate_body ref, + unique int order_bys: @ql_order_bys ref +); + +#keyset[ql_full_aggregate_body, index] +ql_full_aggregate_body_child( + int ql_full_aggregate_body: @ql_full_aggregate_body ref, + int index: int ref, + unique int child: @ql_var_decl ref +); + +ql_full_aggregate_body_def( + unique int id: @ql_full_aggregate_body, + int loc: @location ref +); + +@ql_higherOrderTerm_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_predicate_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_underscore | @ql_unary_expr | @ql_variable + +#keyset[ql_higher_order_term, index] +ql_higher_order_term_child( + int ql_higher_order_term: @ql_higher_order_term ref, + int index: int ref, + unique int child: @ql_higherOrderTerm_child_type ref +); + +ql_higher_order_term_def( + unique int id: @ql_higher_order_term, + int name: @ql_token_literal_id ref, + int loc: @location ref +); + +@ql_if_term_cond_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_if_term_first_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_if_term_second_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_if_term_def( + unique int id: @ql_if_term, + int cond: @ql_if_term_cond_type ref, + int first: @ql_if_term_first_type ref, + int second: @ql_if_term_second_type ref, + int loc: @location ref +); + +@ql_implication_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_implication_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_implication_def( + unique int id: @ql_implication, + int left: @ql_implication_left_type ref, + int right: @ql_implication_right_type ref, + int loc: @location ref +); + +@ql_importDirective_child_type = @ql_import_module_expr | @ql_module_name + +#keyset[ql_import_directive, index] +ql_import_directive_child( + int ql_import_directive: @ql_import_directive ref, + int index: int ref, + unique int child: @ql_importDirective_child_type ref +); + +ql_import_directive_def( + unique int id: @ql_import_directive, + int loc: @location ref +); + +#keyset[ql_import_module_expr, index] +ql_import_module_expr_name( + int ql_import_module_expr: @ql_import_module_expr ref, + int index: int ref, + unique int name: @ql_token_simple_id ref +); + +ql_import_module_expr_def( + unique int id: @ql_import_module_expr, + int child: @ql_qual_module_expr ref, + int loc: @location ref +); + +@ql_in_expr_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_in_expr_right_type = @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_expr_annotation | @ql_literal | @ql_par_expr | @ql_qualified_expr | @ql_range | @ql_set_literal | @ql_super_ref | @ql_variable + +ql_in_expr_def( + unique int id: @ql_in_expr, + int left: @ql_in_expr_left_type ref, + int right: @ql_in_expr_right_type ref, + int loc: @location ref +); + +@ql_instance_of_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_type_expr | @ql_unary_expr | @ql_variable + +#keyset[ql_instance_of, index] +ql_instance_of_child( + int ql_instance_of: @ql_instance_of ref, + int index: int ref, + unique int child: @ql_instance_of_child_type ref +); + +ql_instance_of_def( + unique int id: @ql_instance_of, + int loc: @location ref +); + +@ql_literal_child_type = @ql_bool | @ql_token_float | @ql_token_integer | @ql_token_string + +ql_literal_def( + unique int id: @ql_literal, + int child: @ql_literal_child_type ref, + int loc: @location ref +); + +@ql_memberPredicate_returnType_type = @ql_token_predicate | @ql_type_expr + +@ql_memberPredicate_child_type = @ql_body | @ql_higher_order_term | @ql_token_empty | @ql_var_decl + +#keyset[ql_member_predicate, index] +ql_member_predicate_child( + int ql_member_predicate: @ql_member_predicate ref, + int index: int ref, + unique int child: @ql_memberPredicate_child_type ref +); + +ql_member_predicate_def( + unique int id: @ql_member_predicate, + int name: @ql_token_predicate_name ref, + int return_type: @ql_memberPredicate_returnType_type ref, + int loc: @location ref +); + +@ql_module_child_type = @ql_module_alias_body | @ql_module_member + +#keyset[ql_module, index] +ql_module_child( + int ql_module: @ql_module ref, + int index: int ref, + unique int child: @ql_module_child_type ref +); + +ql_module_def( + unique int id: @ql_module, + int name: @ql_module_name ref, + int loc: @location ref +); + +ql_module_alias_body_def( + unique int id: @ql_module_alias_body, + int child: @ql_module_expr ref, + int loc: @location ref +); + +ql_module_expr_name( + unique int ql_module_expr: @ql_module_expr ref, + unique int name: @ql_token_simple_id ref +); + +@ql_moduleExpr_child_type = @ql_module_expr | @ql_token_simple_id + +ql_module_expr_def( + unique int id: @ql_module_expr, + int child: @ql_moduleExpr_child_type ref, + int loc: @location ref +); + +@ql_moduleMember_child_type = @ql_annotation | @ql_classless_predicate | @ql_dataclass | @ql_datatype | @ql_import_directive | @ql_module | @ql_select | @ql_token_qldoc + +#keyset[ql_module_member, index] +ql_module_member_child( + int ql_module_member: @ql_module_member ref, + int index: int ref, + unique int child: @ql_moduleMember_child_type ref +); + +ql_module_member_def( + unique int id: @ql_module_member, + int loc: @location ref +); + +ql_module_name_def( + unique int id: @ql_module_name, + int child: @ql_token_simple_id ref, + int loc: @location ref +); + +@ql_mul_expr_left_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_mul_expr_right_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_mul_expr_def( + unique int id: @ql_mul_expr, + int left: @ql_mul_expr_left_type ref, + int right: @ql_mul_expr_right_type ref, + int child: @ql_token_mulop ref, + int loc: @location ref +); + +@ql_negation_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_negation_def( + unique int id: @ql_negation, + int child: @ql_negation_child_type ref, + int loc: @location ref +); + +@ql_orderBy_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_direction | @ql_unary_expr | @ql_variable + +#keyset[ql_order_by, index] +ql_order_by_child( + int ql_order_by: @ql_order_by ref, + int index: int ref, + unique int child: @ql_orderBy_child_type ref +); + +ql_order_by_def( + unique int id: @ql_order_by, + int loc: @location ref +); + +#keyset[ql_order_bys, index] +ql_order_bys_child( + int ql_order_bys: @ql_order_bys ref, + int index: int ref, + unique int child: @ql_order_by ref +); + +ql_order_bys_def( + unique int id: @ql_order_bys, + int loc: @location ref +); + +@ql_par_expr_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_par_expr_def( + unique int id: @ql_par_expr, + int child: @ql_par_expr_child_type ref, + int loc: @location ref +); + +ql_predicate_alias_body_def( + unique int id: @ql_predicate_alias_body, + int child: @ql_predicate_expr ref, + int loc: @location ref +); + +@ql_predicateExpr_child_type = @ql_arityless_predicate_expr | @ql_token_integer + +#keyset[ql_predicate_expr, index] +ql_predicate_expr_child( + int ql_predicate_expr: @ql_predicate_expr ref, + int index: int ref, + unique int child: @ql_predicateExpr_child_type ref +); + +ql_predicate_expr_def( + unique int id: @ql_predicate_expr, + int loc: @location ref +); + +@ql_prefix_cast_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_type_expr | @ql_unary_expr | @ql_variable + +#keyset[ql_prefix_cast, index] +ql_prefix_cast_child( + int ql_prefix_cast: @ql_prefix_cast ref, + int index: int ref, + unique int child: @ql_prefix_cast_child_type ref +); + +ql_prefix_cast_def( + unique int id: @ql_prefix_cast, + int loc: @location ref +); + +@ql_ql_child_type = @ql_db_entry | @ql_module_member | @ql_yaml_entry + +#keyset[ql_ql, index] +ql_ql_child( + int ql_ql: @ql_ql ref, + int index: int ref, + unique int child: @ql_ql_child_type ref +); + +ql_ql_def( + unique int id: @ql_ql, + int loc: @location ref +); + +#keyset[ql_qual_module_expr, index] +ql_qual_module_expr_name( + int ql_qual_module_expr: @ql_qual_module_expr ref, + int index: int ref, + unique int name: @ql_token_simple_id ref +); + +ql_qual_module_expr_def( + unique int id: @ql_qual_module_expr, + int loc: @location ref +); + +ql_qualified_rhs_name( + unique int ql_qualified_rhs: @ql_qualified_rhs ref, + unique int name: @ql_token_predicate_name ref +); + +@ql_qualifiedRhs_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_closure | @ql_token_underscore | @ql_type_expr | @ql_unary_expr | @ql_variable + +#keyset[ql_qualified_rhs, index] +ql_qualified_rhs_child( + int ql_qualified_rhs: @ql_qualified_rhs ref, + int index: int ref, + unique int child: @ql_qualifiedRhs_child_type ref +); + +ql_qualified_rhs_def( + unique int id: @ql_qualified_rhs, + int loc: @location ref +); + +@ql_qualified_expr_child_type = @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_expr_annotation | @ql_literal | @ql_par_expr | @ql_qualified_expr | @ql_qualified_rhs | @ql_range | @ql_set_literal | @ql_super_ref | @ql_variable + +#keyset[ql_qualified_expr, index] +ql_qualified_expr_child( + int ql_qualified_expr: @ql_qualified_expr ref, + int index: int ref, + unique int child: @ql_qualified_expr_child_type ref +); + +ql_qualified_expr_def( + unique int id: @ql_qualified_expr, + int loc: @location ref +); + +@ql_quantified_expr_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_quantified_expr( + unique int ql_quantified: @ql_quantified ref, + unique int expr: @ql_quantified_expr_type ref +); + +@ql_quantified_formula_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_quantified_formula( + unique int ql_quantified: @ql_quantified ref, + unique int formula: @ql_quantified_formula_type ref +); + +@ql_quantified_range_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_quantified_range( + unique int ql_quantified: @ql_quantified ref, + unique int range: @ql_quantified_range_type ref +); + +@ql_quantified_child_type = @ql_token_quantifier | @ql_var_decl + +#keyset[ql_quantified, index] +ql_quantified_child( + int ql_quantified: @ql_quantified ref, + int index: int ref, + unique int child: @ql_quantified_child_type ref +); + +ql_quantified_def( + unique int id: @ql_quantified, + int loc: @location ref +); + +@ql_range_lower_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +@ql_range_upper_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_range_def( + unique int id: @ql_range, + int lower: @ql_range_lower_type ref, + int upper: @ql_range_upper_type ref, + int loc: @location ref +); + +@ql_select_child_type = @ql_add_expr | @ql_aggregate | @ql_as_exprs | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_order_bys | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_var_decl | @ql_variable + +#keyset[ql_select, index] +ql_select_child( + int ql_select: @ql_select ref, + int index: int ref, + unique int child: @ql_select_child_type ref +); + +ql_select_def( + unique int id: @ql_select, + int loc: @location ref +); + +@ql_set_literal_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +#keyset[ql_set_literal, index] +ql_set_literal_child( + int ql_set_literal: @ql_set_literal ref, + int index: int ref, + unique int child: @ql_set_literal_child_type ref +); + +ql_set_literal_def( + unique int id: @ql_set_literal, + int loc: @location ref +); + +ql_special_call_def( + unique int id: @ql_special_call, + int child: @ql_token_special_id ref, + int loc: @location ref +); + +@ql_super_ref_child_type = @ql_token_super | @ql_type_expr + +#keyset[ql_super_ref, index] +ql_super_ref_child( + int ql_super_ref: @ql_super_ref ref, + int index: int ref, + unique int child: @ql_super_ref_child_type ref +); + +ql_super_ref_def( + unique int id: @ql_super_ref, + int loc: @location ref +); + +ql_type_alias_body_def( + unique int id: @ql_type_alias_body, + int child: @ql_type_expr ref, + int loc: @location ref +); + +ql_type_expr_name( + unique int ql_type_expr: @ql_type_expr ref, + unique int name: @ql_token_class_name ref +); + +@ql_typeExpr_child_type = @ql_module_expr | @ql_token_dbtype | @ql_token_primitive_type + +ql_type_expr_child( + unique int ql_type_expr: @ql_type_expr ref, + unique int child: @ql_typeExpr_child_type ref +); + +ql_type_expr_def( + unique int id: @ql_type_expr, + int loc: @location ref +); + +#keyset[ql_type_union_body, index] +ql_type_union_body_child( + int ql_type_union_body: @ql_type_union_body ref, + int index: int ref, + unique int child: @ql_type_expr ref +); + +ql_type_union_body_def( + unique int id: @ql_type_union_body, + int loc: @location ref +); + +@ql_unary_expr_child_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token_unop | @ql_unary_expr | @ql_variable + +#keyset[ql_unary_expr, index] +ql_unary_expr_child( + int ql_unary_expr: @ql_unary_expr ref, + int index: int ref, + unique int child: @ql_unary_expr_child_type ref +); + +ql_unary_expr_def( + unique int id: @ql_unary_expr, + int loc: @location ref +); + +@ql_unqual_agg_body_asExprs_type = @ql_as_exprs | @ql_reserved_word + +#keyset[ql_unqual_agg_body, index] +ql_unqual_agg_body_as_exprs( + int ql_unqual_agg_body: @ql_unqual_agg_body ref, + int index: int ref, + unique int as_exprs: @ql_unqual_agg_body_asExprs_type ref +); + +@ql_unqual_agg_body_guard_type = @ql_add_expr | @ql_aggregate | @ql_call_or_unqual_agg_expr | @ql_comp_term | @ql_conjunction | @ql_disjunction | @ql_expr_annotation | @ql_if_term | @ql_implication | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_mul_expr | @ql_negation | @ql_par_expr | @ql_prefix_cast | @ql_qualified_expr | @ql_quantified | @ql_range | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_unary_expr | @ql_variable + +ql_unqual_agg_body_guard( + unique int ql_unqual_agg_body: @ql_unqual_agg_body ref, + unique int guard: @ql_unqual_agg_body_guard_type ref +); + +#keyset[ql_unqual_agg_body, index] +ql_unqual_agg_body_child( + int ql_unqual_agg_body: @ql_unqual_agg_body ref, + int index: int ref, + unique int child: @ql_var_decl ref +); + +ql_unqual_agg_body_def( + unique int id: @ql_unqual_agg_body, + int loc: @location ref +); + +@ql_varDecl_child_type = @ql_type_expr | @ql_var_name + +#keyset[ql_var_decl, index] +ql_var_decl_child( + int ql_var_decl: @ql_var_decl ref, + int index: int ref, + unique int child: @ql_varDecl_child_type ref +); + +ql_var_decl_def( + unique int id: @ql_var_decl, + int loc: @location ref +); + +ql_var_name_def( + unique int id: @ql_var_name, + int child: @ql_token_simple_id ref, + int loc: @location ref +); + +@ql_variable_child_type = @ql_token_result | @ql_token_this | @ql_var_name + +ql_variable_def( + unique int id: @ql_variable, + int child: @ql_variable_child_type ref, + int loc: @location ref +); + +ql_yaml_comment_def( + unique int id: @ql_yaml_comment, + int child: @ql_token_yaml_value ref, + int loc: @location ref +); + +@ql_yaml_entry_child_type = @ql_yaml_comment | @ql_yaml_keyvaluepair | @ql_yaml_listitem + +ql_yaml_entry_def( + unique int id: @ql_yaml_entry, + int child: @ql_yaml_entry_child_type ref, + int loc: @location ref +); + +@ql_yaml_key_child_type = @ql_token_simple_id | @ql_yaml_key + +#keyset[ql_yaml_key, index] +ql_yaml_key_child( + int ql_yaml_key: @ql_yaml_key ref, + int index: int ref, + unique int child: @ql_yaml_key_child_type ref +); + +ql_yaml_key_def( + unique int id: @ql_yaml_key, + int loc: @location ref +); + +ql_yaml_keyvaluepair_def( + unique int id: @ql_yaml_keyvaluepair, + int key__: @ql_yaml_key ref, + int value: @ql_token_yaml_value ref, + int loc: @location ref +); + +ql_yaml_listitem_def( + unique int id: @ql_yaml_listitem, + int child: @ql_token_yaml_value ref, + int loc: @location ref +); + +ql_tokeninfo( + unique int id: @ql_token, + int kind: int ref, + string value: string ref, + int loc: @location ref +); + +case @ql_token.kind of + 0 = @ql_reserved_word +| 1 = @ql_token_addop +| 2 = @ql_token_agg_id +| 3 = @ql_token_annot_name +| 4 = @ql_token_block_comment +| 5 = @ql_token_class_name +| 6 = @ql_token_closure +| 7 = @ql_token_compop +| 8 = @ql_token_db_boolean +| 9 = @ql_token_db_case +| 10 = @ql_token_db_date +| 11 = @ql_token_db_float +| 12 = @ql_token_db_int +| 13 = @ql_token_db_ref +| 14 = @ql_token_db_string +| 15 = @ql_token_db_unique +| 16 = @ql_token_db_varchar +| 17 = @ql_token_dbtype +| 18 = @ql_token_direction +| 19 = @ql_token_empty +| 20 = @ql_token_false +| 21 = @ql_token_float +| 22 = @ql_token_integer +| 23 = @ql_token_line_comment +| 24 = @ql_token_literal_id +| 25 = @ql_token_mulop +| 26 = @ql_token_predicate +| 27 = @ql_token_predicate_name +| 28 = @ql_token_primitive_type +| 29 = @ql_token_qldoc +| 30 = @ql_token_quantifier +| 31 = @ql_token_result +| 32 = @ql_token_simple_id +| 33 = @ql_token_special_id +| 34 = @ql_token_string +| 35 = @ql_token_super +| 36 = @ql_token_this +| 37 = @ql_token_true +| 38 = @ql_token_underscore +| 39 = @ql_token_unop +| 40 = @ql_token_yaml_value +; + + +@ql_ast_node = @ql_add_expr | @ql_aggregate | @ql_annot_arg | @ql_annotation | @ql_arityless_predicate_expr | @ql_as_expr | @ql_as_exprs | @ql_body | @ql_bool | @ql_call_body | @ql_call_or_unqual_agg_expr | @ql_charpred | @ql_class_member | @ql_classless_predicate | @ql_comp_term | @ql_conjunction | @ql_dataclass | @ql_datatype | @ql_datatype_branch | @ql_datatype_branches | @ql_db_annotation | @ql_db_args_annotation | @ql_db_branch | @ql_db_case_decl | @ql_db_col_type | @ql_db_column | @ql_db_entry | @ql_db_repr_type | @ql_db_table | @ql_db_table_name | @ql_db_union_decl | @ql_disjunction | @ql_expr_aggregate_body | @ql_expr_annotation | @ql_field | @ql_full_aggregate_body | @ql_higher_order_term | @ql_if_term | @ql_implication | @ql_import_directive | @ql_import_module_expr | @ql_in_expr | @ql_instance_of | @ql_literal | @ql_member_predicate | @ql_module | @ql_module_alias_body | @ql_module_expr | @ql_module_member | @ql_module_name | @ql_mul_expr | @ql_negation | @ql_order_by | @ql_order_bys | @ql_par_expr | @ql_predicate_alias_body | @ql_predicate_expr | @ql_prefix_cast | @ql_ql | @ql_qual_module_expr | @ql_qualified_expr | @ql_qualified_rhs | @ql_quantified | @ql_range | @ql_select | @ql_set_literal | @ql_special_call | @ql_super_ref | @ql_token | @ql_type_alias_body | @ql_type_expr | @ql_type_union_body | @ql_unary_expr | @ql_unqual_agg_body | @ql_var_decl | @ql_var_name | @ql_variable | @ql_yaml_comment | @ql_yaml_entry | @ql_yaml_key | @ql_yaml_keyvaluepair | @ql_yaml_listitem + +@ql_ast_node_parent = @file | @ql_ast_node #keyset[parent, parent_index] -ast_node_parent( - int child: @ast_node ref, - int parent: @ast_node_parent ref, +ql_ast_node_parent( + int child: @ql_ast_node ref, + int parent: @ql_ast_node_parent ref, int parent_index: int ref ); From 7f80514144b7c49782cfe444620d78aac329b45d Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 09:21:24 +0000 Subject: [PATCH 28/43] Autoformat --- ql/src/codeql_ql/ast/Ast.qll | 80 ++++++---------------- ql/src/codeql_ql/ast/internal/AstNodes.qll | 8 +-- 2 files changed, 22 insertions(+), 66 deletions(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 718ca8707b1..e2537f3d639 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -95,9 +95,7 @@ class TopLevel extends TTopLevel, AstNode { ModuleMember getAMember() { result = getMember(_) } /** Gets the `i`'th member of this top-level module. */ - ModuleMember getMember(int i) { - toQL(result) = file.getChild(i).(QL::ModuleMember).getChild(_) - } + ModuleMember getMember(int i) { toQL(result) = file.getChild(i).(QL::ModuleMember).getChild(_) } /** Gets a top-level import in this module. */ Import getAnImport() { result = this.getAMember() } @@ -171,9 +169,7 @@ class Select extends TSelect, AstNode { Expr getExpr(int i) { toQL(result) = sel.getChild(_).(QL::AsExprs).getChild(i) } // TODO: This gets the `i`th order-by, but some expressions might not have an order-by. - Expr getOrderBy(int i) { - toQL(result) = sel.getChild(_).(QL::OrderBys).getChild(i).getChild(0) - } + Expr getOrderBy(int i) { toQL(result) = sel.getChild(_).(QL::OrderBys).getChild(i).getChild(0) } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -429,11 +425,7 @@ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclarati override VarDecl getParameter(int i) { toQL(result) = - rank[i + 1](QL::VarDecl decl, int index | - decl = pred.getChild(index) - | - decl order by index - ) + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index) } override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() } @@ -476,11 +468,7 @@ class ClassPredicate extends TClassPredicate, Predicate { override VarDecl getParameter(int i) { toQL(result) = - rank[i + 1](QL::VarDecl decl, int index | - decl = pred.getChild(index) - | - decl order by index - ) + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index) } /** @@ -654,22 +642,16 @@ class Module extends TModule, ModuleDeclaration { /** * Gets a member of the module. */ - AstNode getAMember() { - toQL(result) = mod.getChild(_).(QL::ModuleMember).getChild(_) - } + AstNode getAMember() { toQL(result) = mod.getChild(_).(QL::ModuleMember).getChild(_) } - AstNode getMember(int i) { - toQL(result) = mod.getChild(i).(QL::ModuleMember).getChild(_) - } + AstNode getMember(int i) { toQL(result) = mod.getChild(i).(QL::ModuleMember).getChild(_) } QLDoc getQLDocFor(AstNode m) { exists(int i | result = this.getMember(i) and m = this.getMember(i + 1)) } /** Gets the module expression that this module is an alias for, if any. */ - ModuleExpr getAlias() { - toQL(result) = mod.getAFieldOrChild().(QL::ModuleAliasBody).getChild() - } + ModuleExpr getAlias() { toQL(result) = mod.getAFieldOrChild().(QL::ModuleAliasBody).getChild() } override AstNode getAChild(string pred) { result = super.getAChild(pred) @@ -731,14 +713,11 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { /** * Gets the charateristic predicate for this class. */ - CharPred getCharPred() { - toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) - } + CharPred getCharPred() { toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) } AstNode getMember(int i) { toQL(result) = cls.getChild(i).(QL::ClassMember).getChild(_) or - toQL(result) = - cls.getChild(i).(QL::ClassMember).getChild(_).(QL::Field).getChild() + toQL(result) = cls.getChild(i).(QL::ClassMember).getChild(_).(QL::Field).getChild() } QLDoc getQLDocFor(AstNode m) { @@ -764,8 +743,7 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { * Gets a field in this class. */ VarDecl getAField() { - toQL(result) = - cls.getChild(_).(QL::ClassMember).getChild(_).(QL::Field).getChild() + toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_).(QL::Field).getChild() } /** @@ -774,14 +752,10 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { TypeExpr getASuperType() { toQL(result) = cls.getExtends(_) } /** Gets the type that this class is defined to be an alias of. */ - TypeExpr getAliasType() { - toQL(result) = cls.getChild(_).(QL::TypeAliasBody).getChild() - } + TypeExpr getAliasType() { toQL(result) = cls.getChild(_).(QL::TypeAliasBody).getChild() } /** Gets the type of one of the members that this class is defined to be a union of. */ - TypeExpr getUnionMember() { - toQL(result) = cls.getChild(_).(QL::TypeUnionBody).getChild(_) - } + TypeExpr getUnionMember() { toQL(result) = cls.getChild(_).(QL::TypeUnionBody).getChild(_) } /** Gets the class type defined by this class declaration. */ Type getType() { result.getDeclaration() = this } @@ -846,11 +820,7 @@ class NewTypeBranch extends TNewTypeBranch, PredicateOrBuiltin, TypeDeclaration /** Gets a field in this branch. */ VarDecl getField(int i) { toQL(result) = - rank[i + 1](QL::VarDecl var, int index | - var = branch.getChild(index) - | - var order by index - ) + rank[i + 1](QL::VarDecl var, int index | var = branch.getChild(index) | var order by index) } /** Gets the body of this branch. */ @@ -922,9 +892,7 @@ class PredicateCall extends TPredicateCall, Call { PredicateCall() { this = TPredicateCall(expr) } override Expr getArgument(int i) { - exists(QL::CallBody body | body.getParent() = expr | - toQL(result) = body.getChild(i) - ) + exists(QL::CallBody body | body.getParent() = expr | toQL(result) = body.getChild(i)) } final override ModuleExpr getQualifier() { @@ -936,9 +904,7 @@ class PredicateCall extends TPredicateCall, Call { override string getAPrimaryQlClass() { result = "PredicateCall" } - override predicate isClosure(string kind) { - kind = expr.getChild(_).(QL::Closure).getValue() - } + override predicate isClosure(string kind) { kind = expr.getChild(_).(QL::Closure).getValue() } /** * Gets the name of the predicate called. @@ -972,9 +938,7 @@ class MemberCall extends TMemberCall, Call { * Gets the name of the member called. * E.g. for `foo.bar()` the result is "bar". */ - string getMemberName() { - result = expr.getChild(_).(QL::QualifiedRhs).getName().getValue() - } + string getMemberName() { result = expr.getChild(_).(QL::QualifiedRhs).getName().getValue() } override predicate isClosure(string kind) { kind = expr.getChild(_).(QL::QualifiedRhs).getChild(_).(QL::Closure).getValue() @@ -985,9 +949,7 @@ class MemberCall extends TMemberCall, Call { * * Only yields a result if this is actually a `super` call. */ - TypeExpr getSuperType() { - toQL(result) = expr.getChild(_).(QL::SuperRef).getChild(0) - } + TypeExpr getSuperType() { toQL(result) = expr.getChild(_).(QL::SuperRef).getChild(0) } override Expr getArgument(int i) { result = @@ -1053,9 +1015,7 @@ class InlineCast extends TInlineCast, Expr { * Gets the type being cast to. * E.g. for `foo.(Bar)` the result is `Bar`. */ - TypeExpr getTypeExpr() { - toQL(result) = expr.getChild(_).(QL::QualifiedRhs).getChild(_) - } + TypeExpr getTypeExpr() { toQL(result) = expr.getChild(_).(QL::QualifiedRhs).getChild(_) } override Type getType() { result = this.getTypeExpr().getResolvedType() } @@ -2177,8 +2137,8 @@ private class AnnotationArg extends TAnnotationArg, AstNode { string getValue() { result = [ - arg.getChild().(QL::SimpleId).getValue(), - arg.getChild().(QL::Result).getValue(), arg.getChild().(QL::This).getValue() + arg.getChild().(QL::SimpleId).getValue(), arg.getChild().(QL::Result).getValue(), + arg.getChild().(QL::This).getValue() ] } diff --git a/ql/src/codeql_ql/ast/internal/AstNodes.qll b/ql/src/codeql_ql/ast/internal/AstNodes.qll index 8aab6db4189..1b0f92d014e 100644 --- a/ql/src/codeql_ql/ast/internal/AstNodes.qll +++ b/ql/src/codeql_ql/ast/internal/AstNodes.qll @@ -23,12 +23,8 @@ newtype TAstNode = TComparisonFormula(QL::CompTerm comp) or TComparisonOp(QL::Compop op) or TQuantifier(QL::Quantified quant) or - TFullAggregate(QL::Aggregate agg) { - agg.getChild(_) instanceof QL::FullAggregateBody - } or - TExprAggregate(QL::Aggregate agg) { - agg.getChild(_) instanceof QL::ExprAggregateBody - } or + TFullAggregate(QL::Aggregate agg) { agg.getChild(_) instanceof QL::FullAggregateBody } or + TExprAggregate(QL::Aggregate agg) { agg.getChild(_) instanceof QL::ExprAggregateBody } or TSuper(QL::SuperRef sup) or TIdentifier(QL::Variable var) or TAsExpr(QL::AsExpr asExpr) { asExpr.getChild(1) instanceof QL::VarName } or From 741e4a7a385d0d0554247624f88c1ee0562cfdf7 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 11:21:07 +0200 Subject: [PATCH 29/43] add test for qlpacks, and get them to work --- ql/test/callgraph/callgraph.expected | 5 +++++ ql/test/callgraph/callgraph.ql | 2 ++ ql/test/callgraph/packs/lib/LibThing/Foo.qll | 1 + ql/test/callgraph/packs/lib/qlpack.yml | 3 +++ ql/test/callgraph/packs/src/SrcThing.qll | 8 ++++++++ ql/test/callgraph/packs/src/qlpack.yml | 4 ++++ tools/qltest.sh | 2 ++ 7 files changed, 25 insertions(+) create mode 100644 ql/test/callgraph/packs/lib/LibThing/Foo.qll create mode 100644 ql/test/callgraph/packs/lib/qlpack.yml create mode 100644 ql/test/callgraph/packs/src/SrcThing.qll create mode 100644 ql/test/callgraph/packs/src/qlpack.yml diff --git a/ql/test/callgraph/callgraph.expected b/ql/test/callgraph/callgraph.expected index 11bbcc896d1..ec7362a187e 100644 --- a/ql/test/callgraph/callgraph.expected +++ b/ql/test/callgraph/callgraph.expected @@ -1,3 +1,4 @@ +getTarget | Foo.qll:5:26:5:30 | PredicateCall | Foo.qll:3:1:3:26 | ClasslessPredicate foo | | Foo.qll:10:21:10:25 | PredicateCall | Foo.qll:8:3:8:28 | ClassPredicate bar | | Foo.qll:14:30:14:40 | MemberCall | Foo.qll:10:3:10:27 | ClassPredicate baz | @@ -8,3 +9,7 @@ | Foo.qll:31:5:31:12 | PredicateCall | Foo.qll:24:3:24:32 | ClasslessPredicate alias0 | | Foo.qll:36:36:36:65 | MemberCall | file://:0:0:0:0 | replaceAll | | Foo.qll:38:39:38:67 | MemberCall | file://:0:0:0:0 | regexpCapture | +| packs/src/SrcThing.qll:4:3:4:8 | PredicateCall | packs/lib/LibThing/Foo.qll:1:1:1:30 | ClasslessPredicate foo | +| packs/src/SrcThing.qll:5:3:5:8 | PredicateCall | packs/src/SrcThing.qll:8:1:8:30 | ClasslessPredicate bar | +dependsOn +| packs/src/qlpack.yml:1:1:1:4 | ql-testing-src-pack | packs/lib/qlpack.yml:1:1:1:4 | ql-testing-lib-pack | diff --git a/ql/test/callgraph/callgraph.ql b/ql/test/callgraph/callgraph.ql index 4b68e49e361..9ff79048b84 100644 --- a/ql/test/callgraph/callgraph.ql +++ b/ql/test/callgraph/callgraph.ql @@ -1,3 +1,5 @@ import ql query AstNode getTarget(Call call) { result = call.getTarget() } + +query YAML::QLPack dependsOn(YAML::QLPack pack) { result = pack.getADependency() } diff --git a/ql/test/callgraph/packs/lib/LibThing/Foo.qll b/ql/test/callgraph/packs/lib/LibThing/Foo.qll new file mode 100644 index 00000000000..56454a95557 --- /dev/null +++ b/ql/test/callgraph/packs/lib/LibThing/Foo.qll @@ -0,0 +1 @@ +predicate foo(int i) { i = 3 } diff --git a/ql/test/callgraph/packs/lib/qlpack.yml b/ql/test/callgraph/packs/lib/qlpack.yml new file mode 100644 index 00000000000..92e83d1e3d8 --- /dev/null +++ b/ql/test/callgraph/packs/lib/qlpack.yml @@ -0,0 +1,3 @@ +name: ql-testing-lib-pack +version: 0.1.0 +extractor: ql-test-stuff \ No newline at end of file diff --git a/ql/test/callgraph/packs/src/SrcThing.qll b/ql/test/callgraph/packs/src/SrcThing.qll new file mode 100644 index 00000000000..77c812235d9 --- /dev/null +++ b/ql/test/callgraph/packs/src/SrcThing.qll @@ -0,0 +1,8 @@ +import LibThing.Foo + +query predicate test(int i) { + foo(i) and + bar(i) +} + +predicate bar(int i) { i = 4 } diff --git a/ql/test/callgraph/packs/src/qlpack.yml b/ql/test/callgraph/packs/src/qlpack.yml new file mode 100644 index 00000000000..dcf148fe721 --- /dev/null +++ b/ql/test/callgraph/packs/src/qlpack.yml @@ -0,0 +1,4 @@ +name: ql-testing-src-pack +version: 0.1.0 +dependencies: + ql-testing-lib-pack: "*" \ No newline at end of file diff --git a/tools/qltest.sh b/tools/qltest.sh index 19547bfa5df..3712b2a5a10 100755 --- a/tools/qltest.sh +++ b/tools/qltest.sh @@ -6,6 +6,8 @@ exec "${CODEQL_DIST}/codeql" database index-files \ --prune="**/*.testproj" \ --include-extension=.ql \ --include-extension=.qll \ + --include-extension=.dbscheme \ + --include-extension=.yml \ --size-limit=5m \ --language=ql \ --working-dir=.\ From 5ef7b9797edf6391f06956c0e42fabd856cf5e99 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 09:34:14 +0000 Subject: [PATCH 30/43] Also update `qltest.cmd` --- tools/qltest.cmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/qltest.cmd b/tools/qltest.cmd index 374810e3804..2573d4f929e 100644 --- a/tools/qltest.cmd +++ b/tools/qltest.cmd @@ -4,6 +4,8 @@ type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^ --prune=**/*.testproj ^ --include-extension=.ql ^ --include-extension=.qll ^ + --include-extension=.dbscheme ^ + --include-extension=.yml ^ --size-limit=5m ^ --language=ql ^ "%CODEQL_EXTRACTOR_QL_WIP_DATABASE%" From 6c70f5299d9cbc8968df68489933512d623850bc Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 15 Oct 2021 12:05:02 +0200 Subject: [PATCH 31/43] Remove some FPs. --- .../performance/AbstractClassImport.ql | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/ql/src/queries/performance/AbstractClassImport.ql b/ql/src/queries/performance/AbstractClassImport.ql index bccad253561..b3dfceebed1 100644 --- a/ql/src/queries/performance/AbstractClassImport.ql +++ b/ql/src/queries/performance/AbstractClassImport.ql @@ -1,6 +1,8 @@ /** * @name Bidirectional imports for abstract classes - * @description An abstract class should import each of its subclasses, unless it is meant as an extension point, in which case it should import none of them. + * @description An abstract class should import each of its subclasses, unless + * it is meant as a configuration-style class, in which case it + * should import none of them. * @kind problem * @problem.severity error * @id ql/abstract-class-import @@ -19,12 +21,31 @@ File imports(File file) { ) } -/** Gets a non-abstract subclass of `ab` that is defined in a different file */ +predicate testFile(File f) { f.getRelativePath().matches("%/ql/test/%") } + +predicate nonTestQuery(File f) { f.getBaseName().matches("%.ql") and not testFile(f) } + +predicate liveNonTestFile(File f) { + exists(File query | nonTestQuery(query) and f = imports*(query)) +} + +predicate experimentalFile(File f) { f.getRelativePath().matches("%/experimental/%") } + +Class getASubclassOfAbstract(Class ab) { + ab.isAbstract() and + result.getType().getASuperType() = ab.getType() +} + +/** Gets a non-abstract subclass of `ab` that contributes to the extent of `ab`. */ Class concreteExternalSubclass(Class ab) { ab.isAbstract() and not result.isAbstract() and - result.getType().getASuperType*() = ab.getType() and - result.getLocation().getFile() != ab.getLocation().getFile() + result = getASubclassOfAbstract+(ab) and + // Heuristic: An abstract class with subclasses in the same file and no other + // imported subclasses is likely intentional. + result.getLocation().getFile() != ab.getLocation().getFile() and + // Exclude subclasses in tests and libraries that are only used in tests. + liveNonTestFile(result.getLocation().getFile()) } /** Holds if there is a bidirectional import between the abstract class `ab` and its subclass `sub` */ @@ -74,7 +95,9 @@ predicate alert(Class ab, string msg, Class sub, Class sub2) { "This abstract class imports its subclass $@ but doesn't import " + nonImports + " other subclasses, such as $@." ) - ) + ) and + // exclude results in experimental + not experimentalFile(sub.getLocation().getFile()) } from Class ab, string msg, Class sub, Class sub2 From 2a0c29156f52b0ddff24c50e9de152b16bd924ba Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 10:42:44 +0000 Subject: [PATCH 32/43] Update `build.yml` to supply generator args --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4759f095fe..53cd4d7b5fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: run: cargo build --release - name: Generate dbscheme if: ${{ matrix.os == 'ubuntu-latest' }} - run: target/release/ql-generator + run: target/release/ql-generator --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/ - uses: actions/upload-artifact@v2 if: ${{ matrix.os == 'ubuntu-latest' }} with: From 44fff659bdf20fd1614cc1ffebac5ad0e7796dd1 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 10:53:33 +0000 Subject: [PATCH 33/43] Fix `dataset_measure.yml`, hopefully Also I forgot to add `TreeSitter.qll` to the path for the build, whoops. --- .github/workflows/build.yml | 2 +- .github/workflows/dataset_measure.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53cd4d7b5fa..674821b85b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: run: cargo build --release - name: Generate dbscheme if: ${{ matrix.os == 'ubuntu-latest' }} - run: target/release/ql-generator --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/ + run: target/release/ql-generator --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/TreeSitter.qll - uses: actions/upload-artifact@v2 if: ${{ matrix.os == 'ubuntu-latest' }} with: diff --git a/.github/workflows/dataset_measure.yml b/.github/workflows/dataset_measure.yml index 594daf701ab..bbf25377cd7 100644 --- a/.github/workflows/dataset_measure.yml +++ b/.github/workflows/dataset_measure.yml @@ -73,7 +73,7 @@ jobs: path: stats - run: | python -m pip install --user lxml - find stats -name 'stats.xml' | sort | xargs python scripts/merge_stats.py --output ql/src/ql.dbscheme.stats --normalise tokeninfo + find stats -name 'stats.xml' | sort | xargs python scripts/merge_stats.py --output ql/src/ql.dbscheme.stats --normalise ql_tokeninfo - uses: actions/upload-artifact@v2 with: name: ql.dbscheme.stats From 5c70c6a19bae284e7a433e258f9e16616405f50c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 15 Oct 2021 11:57:30 +0100 Subject: [PATCH 34/43] QL: Add dataflow library. --- ql/src/codeql/dataflow/DataFlow.qll | 24 + .../dataflow/internal/DataFlowDispatch.qll | 19 + .../codeql/dataflow/internal/DataFlowImpl.qll | 4559 +++++++++++++++++ .../dataflow/internal/DataFlowImplCommon.qll | 1294 +++++ .../internal/DataFlowImplSpecific.qll | 11 + .../dataflow/internal/DataFlowPrivate.qll | 171 + .../codeql/dataflow/internal/DataFlowUtil.qll | 513 ++ 7 files changed, 6591 insertions(+) create mode 100644 ql/src/codeql/dataflow/DataFlow.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowDispatch.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowImpl.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowPrivate.qll create mode 100644 ql/src/codeql/dataflow/internal/DataFlowUtil.qll diff --git a/ql/src/codeql/dataflow/DataFlow.qll b/ql/src/codeql/dataflow/DataFlow.qll new file mode 100644 index 00000000000..6985e801c52 --- /dev/null +++ b/ql/src/codeql/dataflow/DataFlow.qll @@ -0,0 +1,24 @@ +/** + * Provides a library for local (intra-procedural) and global (inter-procedural) + * data flow analysis: deciding whether data can flow from a _source_ to a + * _sink_. + * + * Unless configured otherwise, _flow_ means that the exact value of + * the source may reach the sink. We do not track flow across pointer + * dereferences or array indexing. To track these types of flow, where the + * exact value may not be preserved, import + * `semmle.code.cpp.dataflow.TaintTracking`. + * + * To use global (interprocedural) data flow, extend the class + * `DataFlow::Configuration` as documented on that class. To use local + * (intraprocedural) data flow between expressions, call + * `DataFlow::localExprFlow`. For more general cases of local data flow, call + * `DataFlow::localFlow` or `DataFlow::localFlowStep` with arguments of type + * `DataFlow::Node`. + */ + +import ql + +module DataFlow { + import internal.DataFlowImpl +} diff --git a/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll b/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll new file mode 100644 index 00000000000..f35f7f66f9e --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll @@ -0,0 +1,19 @@ +private import ql +private import DataFlowUtil + +/** + * Gets a function that might be called by `call`. + */ +DataFlowCallable viableCallable(Call call) { result.asPredicate() = call.getTarget() } + +/** + * Holds if the set of viable implementations that can be called by `call` + * might be improved by knowing the call context. + */ +predicate mayBenefitFromCallContext(Call call, DataFlowCallable f) { none() } + +/** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference. + */ +DataFlowCallable viableImplInCallContext(Call call, Call ctx) { none() } diff --git a/ql/src/codeql/dataflow/internal/DataFlowImpl.qll b/ql/src/codeql/dataflow/internal/DataFlowImpl.qll new file mode 100644 index 00000000000..4ca06c93362 --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowImpl.qll @@ -0,0 +1,4559 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + abstract predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + abstract predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** Holds if data flow through nodes guarded by `guard` is prohibited. */ + predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * Holds if the additional flow step from `node1` to `node2` must be taken + * into account in the analysis. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, Content c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlow(_, sink) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + /** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + int explorationLimit() { none() } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node, this) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink, this) and + dist = node.getSinkDistance() + } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } +} + +private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] + } + +private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() + or + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") + } + + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } + + pragma[nomagic] + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) + } + + pragma[inline] + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) + } + + pragma[nomagic] + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } +} + +private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, int i) { + this.asNode().(ParamNode).isParameterOf(c, i) + } + + int getPosition() { this.isParameterOf(_, result) } +} + +private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } +} + +private predicate inBarrier(NodeEx node, Configuration config) { + exists(Node n | + node.asNode() = n and + config.isBarrierIn(n) and + config.isSource(n) + ) +} + +private predicate outBarrier(NodeEx node, Configuration config) { + exists(Node n | + node.asNode() = n and + config.isBarrierOut(n) and + config.isSink(n) + ) +} + +private predicate fullBarrier(NodeEx node, Configuration config) { + exists(Node n | node.asNode() = n | + config.isBarrier(n) + or + config.isBarrierIn(n) and + not config.isSource(n) + or + config.isBarrierOut(n) and + not config.isSink(n) + or + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + ) +} + +pragma[nomagic] +private predicate sourceNode(NodeEx node, Configuration config) { config.isSource(node.asNode()) } + +pragma[nomagic] +private predicate sinkNode(NodeEx node, Configuration config) { config.isSink(node.asNode()) } + +/** + * Holds if data can flow in one local step from `node1` to `node2`. + */ +private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(n1, n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) + or + exists(Node n | + config.allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) + ) +} + +/** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ +private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + config.isAdditionalFlowStep(n1, n2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) + or + exists(Node n | + config.allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n + ) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ +private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(n1, n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +/** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ +private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + config.isAdditionalFlowStep(n1, n2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { + read(node1.asNode(), c, node2.asNode()) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + config.allowImplicitRead(n, c) + ) +} + +private predicate store( + NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config +) { + store(node1.asNode(), tc, node2.asNode(), contentType) and + read(_, tc.getContent(), _, config) +} + +pragma[nomagic] +private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) +} + +pragma[nomagic] +private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) +} + +/** + * Holds if field flow should be used for the given configuration. + */ +private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } + +private 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(NodeEx node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + sourceNode(node, config) and + cc = false + or + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(NodeEx mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _, config) 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(NodeEx arg | + fwdFlow(arg, _, config) and + viableParamArgEx(_, 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(NodeEx node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, NodeEx node, Cc cc, Configuration config) { + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + read(mid, c, node, config) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Content c, Configuration config) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, _, config) and + store(mid, tc, node, _, config) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(RetNodeEx ret | + fwdFlow(ret, cc, config) and + ret.getReturnPosition() = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOutEx(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { + fwdFlowOut(call, out, 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(ArgNodeEx arg | + fwdFlow(arg, cc, config) and + viableParamArgEx(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(NodeEx node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + sinkNode(node, config) and + toReturn = false + or + exists(NodeEx mid | + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(NodeEx mid | + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + exists(NodeEx mid | + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) + ) + or + // read + exists(NodeEx mid, Content c | + read(node, c, mid, config) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + revFlow(mid, toReturn, pragma[only_bind_into](config)) + ) + or + // flow into a callable + 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(ReturnPosition pos | + revFlowOut(pos, config) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + } + + /** + * Holds if `c` is the target of a read in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Content c, Configuration config) { + exists(NodeEx mid, NodeEx node | + fwdFlow(node, pragma[only_bind_into](config)) and + read(node, c, mid, config) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn, pragma[only_bind_into](config)) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + store(node, tc, mid, _, config) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } + + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, NodeEx out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } + + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config + ) { + viableParamArgEx(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config + ) { + exists(ParamNodeEx p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(NodeEx out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and + revFlow(node2, pragma[only_bind_into](config)) and + store(node1, tc, node2, contentType, config) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { + revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and + revFlow(n2, pragma[only_bind_into](config)) and + read(n1, c, n2, pragma[only_bind_into](config)) + } + + pragma[nomagic] + predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret, config) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + 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 + not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition() + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ +} + +pragma[noinline] +private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { + Stage1::revFlow(node2, config) and + localFlowStep(node1, node2, config) +} + +pragma[noinline] +private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { + Stage1::revFlow(node2, config) and + additionalLocalFlowStep(node1, node2, config) +} + +pragma[nomagic] +private predicate viableReturnPosOutNodeCand1( + DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config +) { + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, NodeEx out, Configuration config +) { + viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and + Stage1::revFlow(ret, config) and + not outBarrier(ret, config) and + not inBarrier(out, config) +} + +pragma[nomagic] +private predicate viableParamArgNodeCand1( + DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config +) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config +) { + viableParamArgNodeCand1(call, p, arg, config) and + Stage1::revFlow(p, config) and + not outBarrier(arg, config) and + not inBarrier(p, config) +} + +/** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int branch(NodeEx n1, Configuration conf) { + result = + strictcount(NodeEx n | + flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) + ) +} + +/** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int join(NodeEx n2, Configuration conf) { + result = + strictcount(NodeEx n | + flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) + ) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, ret, out, config) and + exists(int b, int j | + b = branch(ret, config) and + j = join(out, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config +) { + flowIntoCallNodeCand1(call, arg, p, config) and + exists(int b, int j | + b = branch(arg, config) and + j = join(p, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +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(NodeEx node) { PrevStage::revFlow(node, _) and exists(result) } + + 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 = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } + + private predicate localStep( + NodeEx node1, NodeEx 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(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx 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(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() 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 + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx 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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx 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 fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * 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(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx 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 revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil 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(RetNodeEx 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( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx 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( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} + +pragma[nomagic] +private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) and + Stage2::revFlow(node1, pragma[only_bind_into](config)) +} + +pragma[nomagic] +private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, + Configuration config +) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) and + Stage2::revFlow(node1, pragma[only_bind_into](config)) +} + +private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) + } + } + + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + predicate localFlowEntry(NodeEx node, Configuration config) { + Stage2::revFlow(node, config) and + ( + sourceNode(node, config) or + jumpStep(_, node, config) or + additionalJumpStep(_, node, config) or + node instanceof ParamNodeEx or + node.asNode() instanceof OutNodeExt or + store(_, _, node, _, config) or + read(_, _, node, config) or + node instanceof FlowCheckNode + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, Configuration config) { + exists(NodeEx next | Stage2::revFlow(next, config) | + jumpStep(node, next, config) or + additionalJumpStep(node, next, config) or + flowIntoCallNodeCand1(_, node, next, config) or + flowOutOfCallNodeCand1(_, node, next, config) or + store(node, _, next, _, config) or + read(node, _, next, config) + ) + or + node instanceof FlowCheckNode + or + sinkNode(node, config) + } + + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, NodeEx node2, Configuration config + ) { + additionalLocalFlowStepNodeCand1(node1, node2, config) and + Stage2::revFlow(node1, _, _, false, pragma[only_bind_into](config)) and + Stage2::revFlow(node2, _, _, false, pragma[only_bind_into](config)) + } + + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, NodeEx node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + ( + localFlowEntry(node1, pragma[only_bind_into](config)) and + ( + localFlowStepNodeCand1(node1, node2, config) and + preservesValue = true and + t = node1.getDataFlowType() // irrelevant dummy value + or + additionalLocalFlowStepNodeCand2(node1, node2, config) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + or + exists(NodeEx mid | + localFlowStepPlus(node1, mid, preservesValue, t, pragma[only_bind_into](config), cc) and + localFlowStepNodeCand1(mid, node2, config) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, mid, _, _, pragma[only_bind_into](config), cc) and + additionalLocalFlowStepNodeCand2(mid, node2, config) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + ) + ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, NodeEx node2, boolean preservesValue, AccessPathFrontNil apf, + Configuration config, LocalCallContext callContext + ) { + localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and + localFlowExit(node2, config) + } +} + +private import LocalFlowBigStep + +private module Stage3 { + module PrevStage = Stage2; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + 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 ccNone() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } + + private predicate localStep( + NodeEx node1, NodeEx 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; + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { ap.isClearedAt(node.asNode()) } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, ap] + private predicate filter(NodeEx node, Ap ap) { + not clear(node, ap) and + if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), 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(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + bindingset[result, apa] + private ApApprox unbindApa(ApApprox apa) { + exists(ApApprox apa0 | + apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) + ) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindApa(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx 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(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() 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 + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, unbindApa(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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx 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 fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * 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(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx 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 revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil 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(RetNodeEx 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( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx 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( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ +} + +/** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ +private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) { + exists(AccessPathFront apf | + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config) + ) +} + +/** + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. + */ +private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and + nodes = + strictcount(NodeEx n | + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + or + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes and + not tc.forceHighPrecision() + ) +} + +private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage3::consCand(tc, TFrontNil(t), _) and + not expensiveLen2unfolding(tc, _) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage3::consCand(tc1, TFrontHead(tc2), _) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1, _) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc, _) + } + +/** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); +} + +private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; + + AccessPathApproxNil() { this = TNil(t) } + + override string toString() { result = concat(": " + ppReprType(t)) } + + override TypedContent getHead() { none() } + + override int len() { result = 0 } + + override DataFlowType getType() { result = t } + + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } +} + +abstract private class AccessPathApproxCons extends AccessPathApprox { } + +private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; + + AccessPathApproxConsNil() { this = TConsNil(tc, t) } + + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } + + override TypedContent getHead() { result = tc } + + override int len() { result = 1 } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } +} + +private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; + + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } + + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc1 } + + override int len() { result = len } + + override DataFlowType getType() { result = tc1.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc1) } + + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + } +} + +private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage3::consCand(tc, TFrontNil(t), _) and + result = TNil(t) + ) + ) + } +} + +/** Gets the access path obtained by popping `tc` from `ap`, if any. */ +private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + +/** Gets the access path obtained by pushing `tc` onto `ap`. */ +private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + +private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) + +private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } +} + +private module Stage4 { + module PrevStage = Stage3; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + 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 ccNone() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + private predicate localStep( + NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and + PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and + PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) + } + + bindingset[node, ap] + private predicate filter(NodeEx 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() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + bindingset[result, apa] + private ApApprox unbindApa(ApApprox apa) { + exists(ApApprox apa0 | + apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) + ) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindApa(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx 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(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() 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 + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, unbindApa(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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx 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 fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * 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(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx 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 revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil 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(RetNodeEx 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( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx 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( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ +} + +bindingset[conf, result] +private Configuration unbindConf(Configuration conf) { + exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) +} + +private predicate nodeMayUseSummary(NodeEx n, AccessPathApprox apa, Configuration config) { + exists(DataFlowCallable c, AccessPathApprox apa0 | + 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(ParamNodeEx p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } + +/** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ +abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); +} + +/** A summary context from which no flow summary can be generated. */ +private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } +} + +/** A summary context from which a flow summary can be generated. */ +private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private AccessPath ap; + + SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } + + int getParameterPos() { p.isParameterOf(_, result) } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ +private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + config) + ) + ) +} + +private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { + result = + strictcount(NodeEx n | + Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config) + ) +} + +/** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ +private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = count1to2unfold(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + apLimit < aps and + tupleLimit < (aps - 1) * nodes + ) +} + +private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { + exists(TypedContent head | + apa.pop(head) = result and + Stage4::consCand(head, result, config) + ) +} + +/** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ +private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { + if apa.getHead().forceHighPrecision() + then unfold = true + else + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) +} + +/** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ +private int countAps(AccessPathApprox apa, Configuration config) { + evalUnfold(apa, false, config) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) + or + evalUnfold(apa, false, config) and + result = count1to2unfold(apa, config) and + not expensiveLen1to2unfolding(apa, config) + or + evalUnfold(apa, true, config) and + result = countPotentialAps(apa, config) +} + +/** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ +language[monotonicAggregates] +private int countPotentialAps(AccessPathApprox apa, Configuration config) { + apa instanceof AccessPathApproxNil and result = 1 + or + result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) +} + +private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false, _) and + head = apa.getHead() and + tail.getApprox() = getATail(apa, _) + ) + } or + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + not expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa, _).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head = apa.getHead() + ) + } + +private newtype TPathNode = + TPathNodeMid(NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { + // A PathNode is introduced by a source ... + Stage4::revFlow(node, config) and + sourceNode(node, config) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + exists(PathNodeMid mid | + pathStep(mid, node, cc, sc, ap) and + pragma[only_bind_into](config) = mid.getConfiguration() and + Stage4::revFlow(node, _, _, ap.getApprox(), pragma[only_bind_into](config)) + ) + } or + TPathNodeSink(NodeEx node, Configuration config) { + sinkNode(node, pragma[only_bind_into](config)) and + Stage4::revFlow(node, pragma[only_bind_into](config)) and + ( + // A sink that is also a source ... + sourceNode(node, config) + or + // ... or a sink that can be reached from a source + exists(PathNodeMid mid | + pathStep(mid, node, _, _, TAccessPathNil(_)) and + pragma[only_bind_into](config) = mid.getConfiguration() + ) + ) + } + +/** + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ + abstract string toString(); + + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() + } + + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } +} + +private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } +} + +private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false + ) + } + + override string toString() { + result = "[" + this.toStringImpl(true) + length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) + } +} + +private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage4::consCand(head1, result.getApprox(), _) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } +} + +private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; + + AccessPathCons1() { this = TAccessPathCons1(head, len) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } + + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } +} + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source are generated. + */ +class PathNode extends TPathNode { + /** Gets a textual representation of this element. */ + string toString() { none() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { none() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.(PathNodeImpl).getNodeEx().projectToNode() = result } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + private PathNode getASuccessorIfHidden() { + this.(PathNodeImpl).isHidden() and + result = this.(PathNodeImpl).getASuccessorImpl() + } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { + result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and + not this.(PathNodeImpl).isHidden() and + not result.(PathNodeImpl).isHidden() + } + + /** Holds if this node is a source. */ + predicate isSource() { none() } +} + +abstract private class PathNodeImpl extends PathNode { + abstract PathNode getASuccessorImpl(); + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } + + override string toString() { result = this.getNodeEx().toString() + ppAp() } + + override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** Holds if `n` can reach a sink. */ +private predicate directReach(PathNode n) { + n instanceof PathNodeSink or directReach(n.getASuccessor()) +} + +/** Holds if `n` can reach a sink or is used in a subpath. */ +private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) } + +/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) } + +private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) + +/** + * Provides the query predicates needed to include a graph in a path-problem query. + */ +module PathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(b) } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + reach(n) and key = "semmle.label" and val = n.toString() + } + + query predicate subpaths = Subpaths::subpaths/4; +} + +/** + * An intermediate flow graph node. This is a triple consisting of a `Node`, + * a `CallContext`, and a `Configuration`. + */ +private class PathNodeMid extends PathNodeImpl, TPathNodeMid { + NodeEx node; + CallContext cc; + SummaryCtx sc; + AccessPath ap; + Configuration config; + + PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) } + + override NodeEx getNodeEx() { result = node } + + CallContext getCallContext() { result = cc } + + SummaryCtx getSummaryCtx() { result = sc } + + AccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx(), + result.getAp()) and + result.getConfiguration() = unbindConf(this.getConfiguration()) + } + + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = getSuccMid() + or + // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges + exists(PathNodeMid mid, PathNodeSink sink | + mid = getSuccMid() and + mid.getNodeEx() = sink.getNodeEx() and + mid.getAp() instanceof AccessPathNil and + sink.getConfiguration() = unbindConf(mid.getConfiguration()) and + result = sink + ) + } + + override predicate isSource() { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap instanceof AccessPathNil + } +} + +/** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ +private class PathNodeSink extends PathNodeImpl, TPathNodeSink { + NodeEx node; + Configuration config; + + PathNodeSink() { this = TPathNodeSink(node, config) } + + override NodeEx getNodeEx() { result = node } + + override Configuration getConfiguration() { result = config } + + override PathNode getASuccessorImpl() { none() } + + override predicate isSource() { sourceNode(node, config) } +} + +/** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ +private predicate pathStep( + PathNodeMid mid, NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap +) { + exists(AccessPath ap0, NodeEx midnode, Configuration conf, LocalCallContext localCC | + midnode = mid.getNodeEx() and + conf = mid.getConfiguration() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap0 = mid.getAp() + | + localFlowBigStep(midnode, node, true, _, conf, localCC) and + ap = ap0 + or + localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() +} + +pragma[nomagic] +private predicate pathReadStep( + PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +pragma[nomagic] +private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa, + Configuration config +) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + apa = mid.getAp().getApprox() and + config = mid.getConfiguration() +} + +pragma[nomagic] +private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa, + Configuration config +) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + pathOutOfCallable0(mid, pos, innercc, apa, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) +} + +pragma[noinline] +private NodeEx getAnOutNodeFlow( + ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config +) { + result.asNode() = kind.getAnOutNode(call) and + Stage4::revFlow(result, _, _, apa, config) +} + +/** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ +pragma[noinline] +private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | + pathOutOfCallable1(mid, call, kind, cc, apa, config) and + out = getAnOutNodeFlow(kind, call, apa, config) + ) +} + +/** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ +pragma[noinline] +private predicate pathIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa +) { + exists(ArgNode arg | + arg = mid.getNodeEx().asNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + apa = ap.getApprox() + ) +} + +pragma[noinline] +private predicate parameterCand( + DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config +) { + exists(ParamNodeEx p | + Stage4::revFlow(p, _, _, apa, config) and + p.isParameterOf(callable, i) + ) +} + +pragma[nomagic] +private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + AccessPath ap +) { + exists(AccessPathApprox apa | + pathIntoArg(mid, i, outercc, call, ap, apa) and + callable = resolveCall(call, outercc) and + parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration()) + ) +} + +/** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ +private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + DataFlowCall call +) { + exists(int i, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, i, outercc, call, ap) and + p.isParameterOf(callable, i) and + ( + sc = TSummaryCtxSome(p, ap) + or + not exists(TSummaryCtxSome(p, ap)) and + sc = TSummaryCtxNone() + ) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) +} + +/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ +pragma[nomagic] +private predicate paramFlowsThrough( + ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa, + Configuration config +) { + exists(PathNodeMid mid, RetNodeEx ret, int pos | + mid.getNodeEx() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + config = mid.getConfiguration() and + ap = mid.getAp() and + apa = ap.getApprox() and + pos = sc.getParameterPos() and + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) +} + +pragma[nomagic] +private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap, + AccessPathApprox apa +) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration())) + ) +} + +/** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ +pragma[noinline] +private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration())) + ) +} + +private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, AccessPath apout + ) { + pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, innercc, sc, _) and + paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, + unbindConf(arg.getConfiguration())) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + pragma[nomagic] + private Configuration getPathNodeConf(PathNode n) { result = n.getConfiguration() } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNode arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, apout) and + ret.getNodeEx() = retnode and + kind = retnode.getKind() and + innercc = ret.getCallContext() and + sc = ret.getSummaryCtx() and + ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and + apout = ret.getAp() and + not ret.isHidden() + ) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) { + exists(ParamNodeEx p, NodeEx o, AccessPath apout | + pragma[only_bind_into](arg).getASuccessor() = par and + pragma[only_bind_into](arg).getASuccessor() = out and + subpaths03(arg, p, ret, o, apout) and + par.getNodeEx() = p and + out.getNodeEx() = o and + out.getAp() = apout + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath. + */ + predicate retReach(PathNode n) { + subpaths(_, _, n, _) + or + exists(PathNode mid | + retReach(mid) and + n.getASuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +private predicate flowsTo( + PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration +) { + flowsource.isSource() and + flowsource.getConfiguration() = configuration and + flowsource.(PathNodeImpl).getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +predicate flowsTo(Node source, Node sink, Configuration configuration) { + flowsTo(_, _, source, sink, configuration) +} + +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + 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(NodeEx node1, NodeEx node2 | + jumpStep(node1, node2, config) + or + additionalJumpStep(node1, node2, config) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSource(n) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | + interestingCallableSrc(mid, config) and callableStep(mid, c, config) + ) + } + + private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSink(n) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | + interestingCallableSink(mid, config) and callableStep(c, mid, config) + ) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c, Configuration config) { + interestingCallableSrc(c, config) or + interestingCallableSink(c, config) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | + callableStep(c1, c2, config) and + ce1 = TCallable(c1, pragma[only_bind_into](config)) and + ce2 = TCallable(c2, pragma[only_bind_into](config)) + ) + or + exists(Node n, Configuration config | + ce1 = TCallableSrc() and + config.isSource(n) and + ce2 = TCallable(getNodeEnclosingCallable(n), config) + ) + or + exists(Node n, Configuration config | + ce2 = TCallableSink() and + config.isSink(n) and + ce1 = TCallable(getNodeEnclosingCallable(n), config) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c, Configuration config) { + result = distSrcExt(TCallable(c, config)) - 1 + } + + private int distSink(DataFlowCallable c, Configuration config) { + result = distSinkExt(TCallable(c, config)) - 1 + } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = TPartialNil(node.getDataFlowType()) and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and + distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap, + Configuration config + ) { + sinkNode(node, config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + exists(PartialPathNodeRev mid | + revPartialPathStep(mid, node, sc1, sc2, ap, config) and + not clearsContentCached(node.asNode(), ap.getHead()) and + not fullBarrier(node, config) and + distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + ) + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStep(mid, node, cc, sc1, sc2, ap, config) and + not fullBarrier(node, config) and + not clearsContentCached(node.asNode(), ap.getHead().getContent()) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + ) + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { + result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) + } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { + result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) + } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + PartialAccessPath ap; + Configuration config; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) } + + NodeEx getNodeEx() { result = node } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + PartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx1(), + result.getSummaryCtx2(), result.getAp(), result.getConfiguration()) + } + + predicate isSource() { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + RevPartialAccessPath ap; + Configuration config; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) } + + NodeEx getNodeEx() { result = node } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + RevPartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getSummaryCtx1(), this.getSummaryCtx2(), + this.getAp(), this.getConfiguration()) + } + + predicate isSink() { + sinkNode(node, config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStep(mid.getNodeEx(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(mid.getNodeEx(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) and + config = mid.getConfiguration() + ) + or + jumpStep(mid.getNodeEx(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(mid.getNodeEx(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) and + config = mid.getConfiguration() + or + partialPathStoreStep(mid, _, _, node, ap) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsFwd(ap, tc, ap0, config) + ) + or + partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) + or + partialPathOutOfCallable(mid, node, cc, ap, config) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() + or + partialPathThroughCallable(mid, node, cc, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + bindingset[result, i] + private int unbindInt(int i) { i <= result and i >= result } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + store(midNode, tc, node, contentType, mid.getConfiguration()) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd( + PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStoreStep(mid, ap1, tc, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, + Configuration config + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and + ap.getHead() = tc and + pragma[only_bind_into](config) = mid.getConfiguration() and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap, + Configuration config + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + ap = mid.getAp() and + config = mid.getConfiguration() + } + + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, innercc, ap, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } + + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, cc, ap, config) + | + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(ArgNode arg | + arg = mid.getNodeEx().asNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc, + DataFlowCall call, PartialAccessPath ap, Configuration config + ) { + partialPathIntoArg(mid, i, outercc, call, ap, config) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, + TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(int i, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and + p.isParameterOf(callable, i) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + partialPathIntoCallable(mid, _, cc, innercc, sc1, sc2, call, _, config) and + paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, cc, ap, config) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, + RevPartialAccessPath ap, Configuration config + ) { + localFlowStep(node, mid.getNodeEx(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(node, mid.getNodeEx(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + jumpStep(node, mid.getNodeEx(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(node, mid.getNodeEx(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + revPartialPathReadStep(mid, _, _, node, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsRev(ap, c, ap0, config) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode, mid.getConfiguration()) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } + + pragma[nomagic] + private predicate apConsRev( + RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeRev mid | + revPartialPathReadStep(mid, ap1, c, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + store(node, tc, midNode, _, config) and + ap.getHead() = c and + config = mid.getConfiguration() and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + DataFlowCall call, RevPartialAccessPath ap, Configuration config + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(ap) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p | + mid.getNodeEx() = p and + p.getPosition() = pos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap, + Configuration config + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 | + revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and + revPartialPathFlowsThrough(pos, sc1, sc2, ap, config) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, RevPartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, int pos | + revPartialPathThroughCallable0(call, mid, pos, ap, config) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } +} + +import FlowExploration + +private predicate partialFlow( + PartialPathNode source, PartialPathNode node, Configuration configuration +) { + source.getConfiguration() = configuration and + source.isFwdSource() and + node = source.getASuccessor+() +} + +private predicate revPartialFlow( + PartialPathNode node, PartialPathNode sink, Configuration configuration +) { + sink.getConfiguration() = configuration and + sink.isRevSink() and + node.getASuccessor+() = sink +} diff --git a/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll b/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll new file mode 100644 index 00000000000..ea987acb2ea --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll @@ -0,0 +1,1294 @@ +private import DataFlowImplSpecific::Private +private import DataFlowImplSpecific::Public +import Cached + +/** + * The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion. + * + * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the + * estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of + * these limits are represented with lower precision during pruning. + */ +predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) { + apLimit = 10 and + tupleLimit = 10000 +} + +/** + * The cost limits for the `AccessPathApprox` to `AccessPath` expansion. + * + * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the + * estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of + * these limits are represented with lower precision. + */ +predicate accessPathCostLimits(int apLimit, int tupleLimit) { + apLimit = 5 and + tupleLimit = 1000 +} + +/** + * Provides a simple data-flow analysis for resolving lambda calls. The analysis + * currently excludes read-steps, store-steps, and flow-through. + * + * The analysis uses non-linear recursion: When computing a flow path in or out + * of a call, we use the results of the analysis recursively to resolve lambda + * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. + */ +private module LambdaFlow { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallable(call), i) + } + + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallableLambda(call, _), i) + } + + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParamNonLambda(call, i, p) and + arg.argumentOf(call, i) + ) + } + + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParamLambda(call, i, p) and + arg.argumentOf(call, i) + ) + } + + private newtype TReturnPositionSimple = + TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) { + exists(ReturnNode ret | + c = getNodeEnclosingCallable(ret) and + kind = ret.getKind() + ) + } + + pragma[noinline] + private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) { + result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind) + } + + pragma[nomagic] + private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) { + result = TReturnPositionSimple0(viableCallable(call), kind) + } + + pragma[nomagic] + private TReturnPositionSimple viableReturnPosLambda( + DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind + ) { + result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind) + } + + private predicate viableReturnPosOutNonLambda( + DataFlowCall call, TReturnPositionSimple pos, OutNode out + ) { + exists(ReturnKind kind | + pos = viableReturnPosNonLambda(call, kind) and + out = getAnOutNode(call, kind) + ) + } + + private predicate viableReturnPosOutLambda( + DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out + ) { + exists(ReturnKind kind | + pos = viableReturnPosLambda(call, lastCall, kind) and + out = getAnOutNode(call, kind) + ) + } + + /** + * Holds if data can flow (inter-procedurally) from `node` (of type `t`) to + * the lambda call `lambdaCall`. + * + * The parameter `toReturn` indicates whether the path from `node` to + * `lambdaCall` goes through a return, and `toJump` whether the path goes + * through a jump step. + * + * The call context `lastCall` records the last call on the path from `node` + * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing + * callable of `lambdaCall`. + */ + pragma[nomagic] + predicate revLambdaFlow( + DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, + boolean toJump, DataFlowCallOption lastCall + ) { + revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) + else any() + } + + pragma[nomagic] + predicate revLambdaFlow0( + DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, + boolean toJump, DataFlowCallOption lastCall + ) { + lambdaCall(lambdaCall, kind, node) and + t = getNodeDataFlowType(node) and + toReturn = false and + toJump = false and + lastCall = TDataFlowCallNone() + or + // local flow + exists(Node mid, DataFlowType t0 | + revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall) + | + simpleLocalFlowStep(node, mid) and + t = t0 + or + exists(boolean preservesValue | + additionalLambdaFlowStep(node, mid, preservesValue) and + getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) + | + preservesValue = false and + t = getNodeDataFlowType(node) + or + preservesValue = true and + t = t0 + ) + ) + or + // jump step + exists(Node mid, DataFlowType t0 | + revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and + toReturn = false and + toJump = true and + lastCall = TDataFlowCallNone() + | + jumpStepCached(node, mid) and + t = t0 + or + exists(boolean preservesValue | + additionalLambdaFlowStep(node, mid, preservesValue) and + getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) + | + preservesValue = false and + t = getNodeDataFlowType(node) + or + preservesValue = true and + t = t0 + ) + ) + or + // flow into a callable + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | + revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and + ( + if lastCall0 = TDataFlowCallNone() and toJump = false + then lastCall = TDataFlowCallSome(call) + else lastCall = lastCall0 + ) and + toReturn = false + | + viableParamArgNonLambda(call, p, node) + or + viableParamArgLambda(call, p, node) // non-linear recursion + ) + or + // flow out of a callable + exists(TReturnPositionSimple pos | + revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and + getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and + toReturn = true + ) + } + + pragma[nomagic] + predicate revLambdaFlowOutLambdaCall( + DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump, + DataFlowCall call, DataFlowCallOption lastCall + ) { + revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and + exists(ReturnKindExt rk | + out = rk.getAnOutNode(call) and + lambdaCall(call, _, _) + ) + } + + pragma[nomagic] + predicate revLambdaFlowOut( + DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t, + boolean toJump, DataFlowCallOption lastCall + ) { + exists(DataFlowCall call, OutNode out | + revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and + viableReturnPosOutNonLambda(call, pos, out) + or + // non-linear recursion + revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and + viableReturnPosOutLambda(call, _, pos, out) + ) + } + + pragma[nomagic] + predicate revLambdaFlowIn( + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, + DataFlowCallOption lastCall + ) { + revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) + } +} + +private DataFlowCallable viableCallableExt(DataFlowCall call) { + result = viableCallable(call) + or + result = viableCallableLambda(call, _) +} + +cached +private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + + /** + * Gets a viable target for the lambda call `call`. + * + * `lastCall` records the call required to reach `call` in order for the result + * to be a viable target, if any. + */ + cached + DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) { + exists(Node creation, LambdaCallKind kind | + LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and + lambdaCreation(creation, kind, result) + ) + } + + /** + * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. + * The instance parameter is considered to have index `-1`. + */ + pragma[nomagic] + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallableExt(call), i) + } + + /** + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. + */ + cached + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParam(call, i, p) and + arg.argumentOf(call, i) and + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) + ) + } + + pragma[nomagic] + private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { + viableCallableExt(call) = result.getCallable() and + kind = result.getKind() + } + + /** + * Holds if a value at return position `pos` can be returned to `out` via `call`, + * taking virtual dispatch into account. + */ + cached + predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) { + exists(ReturnKindExt kind | + pos = viableReturnPos(call, kind) and + out = kind.getAnOutNode(call) + ) + } + + /** Provides predicates for calculating flow-through summaries. */ + private module FlowThrough { + /** + * The first flow-through approximation: + * + * - Input access paths are abstracted with a Boolean parameter + * that indicates (non-)emptiness. + */ + private module Cand { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps. + * + * `read` indicates whether it is contents of `p` that can flow to `node`. + */ + pragma[nomagic] + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { + p = node and + read = false + or + // local flow + exists(Node mid | + parameterValueFlowCand(p, mid, read) and + simpleLocalFlowStep(mid, node) + ) + or + // read + exists(Node mid | + parameterValueFlowCand(p, mid, false) and + read(mid, _, node) and + read = true + ) + or + // flow through: no prior read + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, false) and + argumentValueFlowsThroughCand(arg, node, read) + ) + or + // flow through: no read inside method + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, read) and + argumentValueFlowsThroughCand(arg, node, false) + ) + } + + pragma[nomagic] + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { + parameterValueFlowCand(p, arg, read) + } + + pragma[nomagic] + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { + parameterValueFlowCand(p, n.getPreUpdateNode(), false) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + * + * `read` indicates whether it is contents of `p` that can flow to the return + * node. + */ + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { + exists(ReturnNode ret | + parameterValueFlowCand(p, ret, read) and + kind = ret.getKind() + ) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughCand0( + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read + ) { + exists(ParamNode param | viableParamArg(call, param, arg) | + parameterValueFlowReturnCand(param, kind, read) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + * + * `read` indicates whether it is contents of `arg` that can flow to `out`. + */ + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThroughCand0(call, arg, kind, read) and + out = getAnOutNode(call, kind) + ) + } + + predicate cand(ParamNode p, Node n) { + parameterValueFlowCand(p, n, _) and + ( + parameterValueFlowReturnCand(p, _, _) + or + parameterValueFlowsToPreUpdateCand(p, _) + ) + } + } + + /** + * The final flow-through calculation: + * + * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`) + * or summarized as a single read step with before and after types recorded + * in the `ReadStepTypesOption` parameter. + * - Types are checked using the `compatibleTypes()` relation. + */ + private module Final { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { + parameterValueFlow0(p, node, read) and + if node instanceof CastingNode + then + // normal flow through + read = TReadStepTypesNone() and + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) + or + // getter + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) + else any() + } + + pragma[nomagic] + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { + p = node and + Cand::cand(p, _) and + read = TReadStepTypesNone() + or + // local flow + exists(Node mid | + parameterValueFlow(p, mid, read) and + simpleLocalFlowStep(mid, node) + ) + or + // read + exists(Node mid | + parameterValueFlow(p, mid, TReadStepTypesNone()) and + readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, + read.getContentType()) and + Cand::parameterValueFlowReturnCand(p, _, true) and + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) + ) + or + parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) + } + + pragma[nomagic] + private predicate parameterValueFlow0_0( + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read + ) { + // flow through: no prior read + exists(ArgNode arg | + parameterValueFlowArg(p, arg, mustBeNone) and + argumentValueFlowsThrough(arg, read, node) + ) + or + // flow through: no read inside method + exists(ArgNode arg | + parameterValueFlowArg(p, arg, read) and + argumentValueFlowsThrough(arg, mustBeNone, node) + ) + } + + pragma[nomagic] + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { + parameterValueFlow(p, arg, read) and + Cand::argumentValueFlowsThroughCand(arg, _, _) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read + ) { + exists(ParamNode param | viableParamArg(call, param, arg) | + parameterValueFlowReturn(param, kind, read) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + pragma[nomagic] + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThrough0(call, arg, kind, read) and + out = getAnOutNode(call, kind) + | + // normal flow through + read = TReadStepTypesNone() and + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) + or + // getter + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and a single read step, not taking call + * contexts into account, thus representing a getter-step. + */ + predicate getterStep(ArgNode arg, Content c, Node out) { + argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps and possibly a single read + * step. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + private predicate parameterValueFlowReturn( + ParamNode p, ReturnKind kind, ReadStepTypesOption read + ) { + exists(ReturnNode ret | + parameterValueFlow(p, ret, read) and + kind = ret.getKind() + ) + } + } + + import Final + } + + import FlowThrough + + cached + private module DispatchWithCallContext { + /** + * Holds if the set of viable implementations that can be called by `call` + * might be improved by knowing the call context. + */ + pragma[nomagic] + private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { + mayBenefitFromCallContext(call, callable) + or + callEnclosingCallable(call, callable) and + exists(viableCallableLambda(call, TDataFlowCallSome(_))) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference. + */ + pragma[nomagic] + private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContext(call, ctx) + or + result = viableCallableLambda(call, TDataFlowCallSome(ctx)) + or + exists(DataFlowCallable enclosing | + mayBenefitFromCallContextExt(call, enclosing) and + enclosing = viableCallableExt(ctx) and + result = viableCallableLambda(call, TDataFlowCallNone()) + ) + } + + /** + * Holds if the call context `ctx` reduces the set of viable run-time + * dispatch targets of call `call` in `c`. + */ + cached + predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) { + exists(int tgts, int ctxtgts | + mayBenefitFromCallContextExt(call, c) and + c = viableCallableExt(ctx) and + ctxtgts = count(viableImplInCallContextExt(call, ctx)) and + tgts = strictcount(viableCallableExt(call)) and + ctxtgts < tgts + ) + } + + /** + * Gets a viable run-time dispatch target for the call `call` in the + * context `ctx`. This is restricted to those calls for which a context + * makes a difference. + */ + cached + DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContextExt(call, ctx) and + reducedViableImplInCallContext(call, _, ctx) + } + + /** + * Holds if flow returning from callable `c` to call `call` might return + * further and if this path restricts the set of call sites that can be + * returned to. + */ + cached + predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) { + exists(int tgts, int ctxtgts | + mayBenefitFromCallContextExt(call, _) and + c = viableCallableExt(call) and + ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and + ctxtgts < tgts + ) + } + + /** + * Gets a viable run-time dispatch target for the call `call` in the + * context `ctx`. This is restricted to those calls and results for which + * the return flow from the result to `call` restricts the possible context + * `ctx`. + */ + cached + DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContextExt(call, ctx) and + reducedViableImplInReturn(result, call) + } + } + + import DispatchWithCallContext + + /** + * Holds if `p` can flow to the pre-update node associated with post-update + * node `n`, in the same callable, using only value-preserving steps. + */ + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { + parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) + } + + private predicate store( + Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType + ) { + storeStep(node1, c, node2) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) + or + exists(Node n1, Node n2 | + n1 = node1.(PostUpdateNode).getPreUpdateNode() and + n2 = node2.(PostUpdateNode).getPreUpdateNode() + | + argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) + or + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) + ) + } + + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + + /** + * Holds if data can flow from `node1` to `node2` via a direct assignment to + * `f`. + * + * This includes reverse steps through reads when the result of the read has + * been stored into, in order to handle cases like `x.f1.f2 = y`. + */ + cached + predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) { + store(node1, tc.getContent(), node2, contentType, tc.getContainerType()) + } + + /** + * Holds if data can flow from `fromNode` to `toNode` because they are the post-update + * nodes of some function output and input respectively, where the output and input + * are aliases. A typical example is a function returning `this`, implementing a fluent + * interface. + */ + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { + exists(Node fromPre, Node toPre | + fromPre = fromNode.getPreUpdateNode() and + toPre = toNode.getPreUpdateNode() + | + exists(DataFlowCall c | + // Does the language-specific simpleLocalFlowStep already model flow + // from function input to output? + fromPre = getAnOutNode(c, _) and + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) + ) + or + argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) + ) + } + + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + + /** + * Holds if the call context `call` improves virtual dispatch in `callable`. + */ + cached + predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) { + reducedViableImplInCallContext(_, callable, call) + } + + /** + * Holds if the call context `call` allows us to prune unreachable nodes in `callable`. + */ + cached + predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) { + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) + } + + cached + newtype TCallContext = + TAnyCallContext() or + TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or + TSomeCall() or + TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } + + cached + newtype TReturnPosition = + TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) { + exists(ReturnNodeExt ret | + c = returnNodeGetEnclosingCallable(ret) and + kind = ret.getKind() + ) + } + + cached + newtype TLocalFlowCallContext = + TAnyLocalCall() or + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } + + cached + newtype TReturnKindExt = + TValueReturn(ReturnKind kind) or + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } + + cached + newtype TBooleanOption = + TBooleanNone() or + TBooleanSome(boolean b) { b = true or b = false } + + cached + newtype TDataFlowCallOption = + TDataFlowCallNone() or + TDataFlowCallSome(DataFlowCall call) + + cached + newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) } + + cached + newtype TAccessPathFront = + TFrontNil(DataFlowType t) or + TFrontHead(TypedContent tc) + + cached + newtype TAccessPathFrontOption = + TAccessPathFrontNone() or + TAccessPathFrontSome(AccessPathFront apf) +} + +/** + * Holds if the call context `call` either improves virtual dispatch in + * `callable` or if it allows us to prune unreachable nodes in `callable`. + */ +predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { + recordDataFlowCallSiteDispatch(call, callable) or + recordDataFlowCallSiteUnreachable(call, callable) +} + +/** + * A `Node` at which a cast can occur such that the type should be checked. + */ +class CastingNode extends Node { + CastingNode() { castingNode(this) } +} + +private predicate readStepWithTypes( + Node n1, DataFlowType container, Content c, Node n2, DataFlowType content +) { + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) +} + +private newtype TReadStepTypesOption = + TReadStepTypesNone() or + TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) { + readStepWithTypes(_, container, c, _, content) + } + +private class ReadStepTypesOption extends TReadStepTypesOption { + predicate isSome() { this instanceof TReadStepTypesSome } + + DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) } + + Content getContent() { this = TReadStepTypesSome(_, result, _) } + + DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) } + + string toString() { if this.isSome() then result = "Some(..)" else result = "None()" } +} + +/** + * A call context to restrict the targets of virtual dispatch, prune local flow, + * and match the call sites of flow into a method with flow out of a method. + * + * There are four cases: + * - `TAnyCallContext()` : No restrictions on method flow. + * - `TSpecificCall(DataFlowCall call)` : Flow entered through the + * given `call`. This call improves the set of viable + * dispatch targets for at least one method call in the current callable + * or helps prune unreachable nodes in the current callable. + * - `TSomeCall()` : Flow entered through a parameter. The + * originating call does not improve the set of dispatch targets for any + * method call in the current callable and was therefore not recorded. + * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and + * this dispatch target of `call` implies a reduced set of dispatch origins + * to which data may flow if it should reach a `return` statement. + */ +abstract class CallContext extends TCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); +} + +abstract class CallContextNoCall extends CallContext { } + +class CallContextAny extends CallContextNoCall, TAnyCallContext { + override string toString() { result = "CcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } +} + +abstract class CallContextCall extends CallContext { + /** Holds if this call context may be `call`. */ + bindingset[call] + abstract predicate matchesCall(DataFlowCall call); +} + +class CallContextSpecificCall extends CallContextCall, TSpecificCall { + override string toString() { + exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")") + } + + override predicate relevantFor(DataFlowCallable callable) { + recordDataFlowCallSite(this.getCall(), callable) + } + + override predicate matchesCall(DataFlowCall call) { call = this.getCall() } + + DataFlowCall getCall() { this = TSpecificCall(result) } +} + +class CallContextSomeCall extends CallContextCall, TSomeCall { + override string toString() { result = "CcSomeCall" } + + override predicate relevantFor(DataFlowCallable callable) { + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) + } + + override predicate matchesCall(DataFlowCall call) { any() } +} + +class CallContextReturn extends CallContextNoCall, TReturn { + override string toString() { + exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + } + + override predicate relevantFor(DataFlowCallable callable) { + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) + } +} + +/** + * A call context that is relevant for pruning local flow. + */ +abstract class LocalCallContext extends TLocalFlowCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); +} + +class LocalCallContextAny extends LocalCallContext, TAnyLocalCall { + override string toString() { result = "LocalCcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } +} + +class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall { + LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) } + + DataFlowCall call; + + DataFlowCall getCall() { result = call } + + override string toString() { result = "LocalCcCall(" + call + ")" } + + override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) } +} + +private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) +} + +/** + * Gets the local call context given the call context and the callable that + * the contexts apply to. + */ +LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) { + ctx.relevantFor(callable) and + if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable) + then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall() + else result instanceof LocalCallContextAny +} + +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + +/** + * A node from which flow can return to the caller. This is either a regular + * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. + */ +class ReturnNodeExt extends Node { + ReturnNodeExt() { returnNodeExt(this, _) } + + /** Gets the kind of this returned value. */ + ReturnKindExt getKind() { returnNodeExt(this, result) } +} + +/** + * A node to which data can flow from a call. Either an ordinary out node + * or a post-update node associated with a call argument. + */ +class OutNodeExt extends Node { + OutNodeExt() { outNodeExt(this) } +} + +/** + * An extended return kind. A return kind describes how data can be returned + * from a callable. This can either be through a returned value or an updated + * parameter. + */ +abstract class ReturnKindExt extends TReturnKindExt { + /** Gets a textual representation of this return kind. */ + abstract string toString(); + + /** Gets a node corresponding to data flow out of `call`. */ + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } +} + +class ValueReturnKind extends ReturnKindExt, TValueReturn { + private ReturnKind kind; + + ValueReturnKind() { this = TValueReturn(kind) } + + ReturnKind getKind() { result = kind } + + override string toString() { result = kind.toString() } +} + +class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { + private int pos; + + ParamUpdateReturnKind() { this = TParamUpdate(pos) } + + int getPosition() { result = pos } + + override string toString() { result = "param update " + pos } +} + +/** A callable tagged with a relevant return kind. */ +class ReturnPosition extends TReturnPosition0 { + private DataFlowCallable c; + private ReturnKindExt kind; + + ReturnPosition() { this = TReturnPosition0(c, kind) } + + /** Gets the callable. */ + DataFlowCallable getCallable() { result = c } + + /** Gets the return kind. */ + ReturnKindExt getKind() { result = kind } + + /** Gets a textual representation of this return position. */ + string toString() { result = "[" + kind + "] " + c } +} + +/** + * Gets the enclosing callable of `n`. Unlike `n.getEnclosingCallable()`, this + * predicate ensures that joins go from `n` to the result instead of the other + * way around. + */ +pragma[inline] +DataFlowCallable getNodeEnclosingCallable(Node n) { + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +pragma[noinline] +private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) { + result = getNodeEnclosingCallable(ret) +} + +pragma[noinline] +private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) { + result.getCallable() = returnNodeGetEnclosingCallable(ret) and + kind = result.getKind() +} + +pragma[noinline] +ReturnPosition getReturnPosition(ReturnNodeExt ret) { + result = getReturnPosition0(ret, ret.getKind()) +} + +/** + * Checks whether `inner` can return to `call` in the call context `innercc`. + * Assumes a context of `inner = viableCallableExt(call)`. + */ +bindingset[innercc, inner, call] +predicate checkCallContextReturn(CallContext innercc, DataFlowCallable inner, DataFlowCall call) { + innercc instanceof CallContextAny + or + exists(DataFlowCallable c0, DataFlowCall call0 | + callEnclosingCallable(call0, inner) and + innercc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) +} + +/** + * Checks whether `call` can resolve to `calltarget` in the call context `cc`. + * Assumes a context of `calltarget = viableCallableExt(call)`. + */ +bindingset[cc, call, calltarget] +predicate checkCallContextCall(CallContext cc, DataFlowCall call, DataFlowCallable calltarget) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | + if reducedViableImplInCallContext(call, _, ctx) + then calltarget = prunedViableImplInCallContext(call, ctx) + else any() + ) + or + cc instanceof CallContextSomeCall + or + cc instanceof CallContextAny + or + cc instanceof CallContextReturn +} + +/** + * Resolves a return from `callable` in `cc` to `call`. This is equivalent to + * `callable = viableCallableExt(call) and checkCallContextReturn(cc, callable, call)`. + */ +bindingset[cc, callable] +predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { + cc instanceof CallContextAny and callable = viableCallableExt(call) + or + exists(DataFlowCallable c0, DataFlowCall call0 | + callEnclosingCallable(call0, callable) and + cc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) +} + +/** + * Resolves a call from `call` in `cc` to `result`. This is equivalent to + * `result = viableCallableExt(call) and checkCallContextCall(cc, call, result)`. + */ +bindingset[call, cc] +DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | + if reducedViableImplInCallContext(call, _, ctx) + then result = prunedViableImplInCallContext(call, ctx) + else result = viableCallableExt(call) + ) + or + result = viableCallableExt(call) and cc instanceof CallContextSomeCall + or + result = viableCallableExt(call) and cc instanceof CallContextAny + or + result = viableCallableExt(call) and cc instanceof CallContextReturn +} + +/** An optional Boolean value. */ +class BooleanOption extends TBooleanOption { + string toString() { + this = TBooleanNone() and result = "" + or + this = TBooleanSome(any(boolean b | result = b.toString())) + } +} + +/** An optional `DataFlowCall`. */ +class DataFlowCallOption extends TDataFlowCallOption { + string toString() { + this = TDataFlowCallNone() and + result = "(none)" + or + exists(DataFlowCall call | + this = TDataFlowCallSome(call) and + result = call.toString() + ) + } +} + +/** Content tagged with the type of a containing object. */ +class TypedContent extends MkTypedContent { + private Content c; + private DataFlowType t; + + TypedContent() { this = MkTypedContent(c, t) } + + /** Gets the content. */ + Content getContent() { result = c } + + /** Gets the container type. */ + DataFlowType getContainerType() { result = t } + + /** Gets a textual representation of this content. */ + string toString() { result = c.toString() } + + /** + * Holds if access paths with this `TypedContent` at their head always should + * be tracked at high precision. This disables adaptive access path precision + * for such access paths. + */ + predicate forceHighPrecision() { forceHighPrecision(c) } +} + +/** + * The front of an access path. This is either a head or a nil. + */ +abstract class AccessPathFront extends TAccessPathFront { + abstract string toString(); + + abstract DataFlowType getType(); + + abstract boolean toBoolNonEmpty(); + + TypedContent getHead() { this = TFrontHead(result) } + + predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) } +} + +class AccessPathFrontNil extends AccessPathFront, TFrontNil { + private DataFlowType t; + + AccessPathFrontNil() { this = TFrontNil(t) } + + override string toString() { result = ppReprType(t) } + + override DataFlowType getType() { result = t } + + override boolean toBoolNonEmpty() { result = false } +} + +class AccessPathFrontHead extends AccessPathFront, TFrontHead { + private TypedContent tc; + + AccessPathFrontHead() { this = TFrontHead(tc) } + + override string toString() { result = tc.toString() } + + override DataFlowType getType() { result = tc.getContainerType() } + + override boolean toBoolNonEmpty() { result = true } +} + +/** An optional access path front. */ +class AccessPathFrontOption extends TAccessPathFrontOption { + string toString() { + this = TAccessPathFrontNone() and result = "" + or + this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString())) + } +} \ No newline at end of file diff --git a/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll b/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll new file mode 100644 index 00000000000..1e9cfd62da8 --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll @@ -0,0 +1,11 @@ +/** + * Provides QL-specific definitions for use in the data flow library. + */ +module Private { + import DataFlowPrivate + import DataFlowDispatch +} + +module Public { + import DataFlowUtil +} \ No newline at end of file diff --git a/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll b/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll new file mode 100644 index 00000000000..eea5bdadc95 --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll @@ -0,0 +1,171 @@ +private import ql +private import codeql_ql.ast.internal.Builtins +private import DataFlowUtil + +/** + * A data flow node that occurs as the argument of a call and is passed as-is + * to the callable. Arguments that are wrapped in an implicit varargs array + * creation are not included, but the implicitly created array is. + * Instance arguments are also included. + */ +class ArgumentNode extends Node { + ArgumentNode() { exists(Argument arg | this.asExpr() = arg) } + + /** + * Holds if this argument occurs at the given position in the given call. + * The instance argument is considered to have index `-1`. + */ + predicate argumentOf(DataFlowCall call, int pos) { + exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition()) + } + + /** Gets the call in which this node is an argument. */ + DataFlowCall getCall() { this.argumentOf(result, _) } +} + +/** A `ReturnNode` that occurs as the result of a `ReturnStmt`. */ +private class NormalReturnNode extends ReturnNode, ExprNode { + NormalReturnNode() { this.getExpr() instanceof ResultAccess } + + /** Gets the kind of this returned value. */ + override ReturnKind getKind() { result = TNormalReturnKind() } +} + +private class ExprOutNode extends OutNode, ExprNode { + Call call; + + ExprOutNode() { call = this.getExpr() } + + /** Gets the underlying call. */ + override DataFlowCall getCall() { result = call } +} + +/** + * Gets a node that can read the value returned from `call` with return kind + * `kind`. + */ +OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { + result = call.getNode() and + kind = TNormalReturnKind() + or + result.(ArgumentOutNode).getCall() = call and + kind = TParameterOutKind(result.(ArgumentOutNode).getIndex()) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that loses the + * calling context. For example, this would happen with flow through a + * global or static variable. + */ +predicate jumpStep(Node n1, Node n2) { none() } + +/** + * Holds if data can flow from `node1` to `node2` via an assignment to `f`. + * Thus, `node2` references an object with a field `f` that contains the + * value of `node1`. + */ +predicate storeStep(Node node1, Content f, PostUpdateNode node2) { none() } + +/** + * Holds if data can flow from `node1` to `node2` via a read of `f`. + * Thus, `node1` references an object with a field `f` whose value ends up in + * `node2`. + */ +predicate readStep(Node node1, Content f, Node node2) { none() } + +/** + * Holds if values stored inside content `c` are cleared at node `n`. + */ +predicate clearsContent(Node n, Content c) { + none() // stub implementation +} + +/** Gets the type of `n` used for type pruning. */ +Type getNodeType(Node n) { + suppressUnusedNode(n) and + result instanceof IntClass // stub implementation +} + +/** Gets a string representation of a type returned by `getNodeType`. */ +string ppReprType(Type t) { none() } // stub implementation + +/** + * Holds if `t1` and `t2` are compatible, that is, whether data can flow from + * a node of type `t1` to a node of type `t2`. + */ +pragma[inline] +predicate compatibleTypes(Type t1, Type t2) { + any() // stub implementation +} + +private predicate suppressUnusedNode(Node n) { any() } + +////////////////////////////////////////////////////////////////////////////// +// Java QL library compatibility wrappers +////////////////////////////////////////////////////////////////////////////// +/** A node that performs a type cast. */ +class CastNode extends Node { + CastNode() { none() } // stub implementation +} + +class DataFlowExpr = Expr; + +class DataFlowType = Type; + +/** A function call relevant for data flow. */ +class DataFlowCall extends Expr instanceof Call { + /** + * Gets the nth argument for this call. + * + * The range of `n` is from `0` to `getNumberOfArguments() - 1`. + */ + Expr getArgument(int n) { result = super.getArgument(n) } + + /** Gets the data flow node corresponding to this call. */ + ExprNode getNode() { result.getExpr() = this } + + /** Gets the enclosing callable of this call. */ + DataFlowCallable getEnclosingCallable() { result.asPredicate() = this.getEnclosingPredicate() } +} + +predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation + +int accessPathLimit() { result = 5 } + +/** + * Holds if access paths with `c` at their head always should be tracked at high + * precision. This disables adaptive access path precision for such access paths. + */ +predicate forceHighPrecision(Content c) { none() } + +/** 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 + * freshly created object that is not saved in a variable. + * + * This predicate is only used for consistency checks. + */ +predicate isImmutableOrUnobservable(Node n) { none() } + +/** Holds if `n` should be hidden from path explanations. */ +predicate nodeIsHidden(Node n) { none() } + +class LambdaCallKind = Unit; + +/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ +predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() } + +/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */ +predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() } + +/** Extra data-flow steps needed for lambda flow analysis. */ +predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } diff --git a/ql/src/codeql/dataflow/internal/DataFlowUtil.qll b/ql/src/codeql/dataflow/internal/DataFlowUtil.qll new file mode 100644 index 00000000000..7410f5b84cd --- /dev/null +++ b/ql/src/codeql/dataflow/internal/DataFlowUtil.qll @@ -0,0 +1,513 @@ +/** + * Provides QL-specific definitions for use in the data flow library. + */ + +private import ql + +cached +private newtype TNode = + TExprNode(Expr e) or + TOutParameterNode(Parameter p) or + TArgumentOutNode(VarAccess acc, Call call, int pos) { + acc.(Argument).getCall() = call and acc.(Argument).getPosition() = pos + } or + TParameterNode(Parameter p) + +/** An argument to a call. */ +class Argument extends Expr { + Call call; + int pos; + + Argument() { call.getArgument(pos) = this } + + /** Gets the call that has this argument. */ + Call getCall() { result = call } + + /** Gets the position of this argument. */ + int getPosition() { result = pos } +} + +newtype TDataFlowCallable = + TDataFlowPredicate(Predicate p) or + TDataFlowTopLevel() or + TDataFlowNewtypeBranch(NewTypeBranch branch) + +class DataFlowCallable extends TDataFlowCallable { + string toString() { + exists(Predicate p | + this = TDataFlowPredicate(p) and + result = p.toString() + ) + or + this = TDataFlowTopLevel() and + result = "top level" + or + exists(NewTypeBranch branch | + this = TDataFlowNewtypeBranch(branch) and + result = branch.toString() + ) + } + + Predicate asPredicate() { this = TDataFlowPredicate(result) } + + predicate asTopLevel() { this = TDataFlowTopLevel() } + + NewTypeBranch asNewTypeBranch() { this = TDataFlowNewtypeBranch(result) } +} + +private newtype TParameter = + TThisParam(ClassPredicate p) or + TResultParam(Predicate p) { exists(p.getReturnType()) } or + TVarParam(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v } + +class Parameter extends TParameter { + string toString() { this.hasName(result) } + + predicate hasName(string name) { + this instanceof TThisParam and name = "this" + or + this instanceof TResultParam and name = "result" + or + exists(VarDecl v | this = TVarParam(v, _, _) and name = v.toString()) + } + + int getIndex() { + this instanceof TThisParam and result = -1 + or + this instanceof TResultParam and result = -2 + or + this = TVarParam(_, result, _) + } + + Predicate getPredicate() { + this = TThisParam(result) + or + this = TResultParam(result) + or + this = TVarParam(_, _, result) + } + + Type getType() { + exists(ClassPredicate cp | + this = TThisParam(cp) and + result = cp.getDeclaringType() + ) + or + exists(Predicate p | + this = TResultParam(p) and + result = p.getReturnType() + ) + or + exists(VarDecl v | + this = TVarParam(v, _, _) and + result = v.getType() + ) + } + + Location getLocation() { + exists(ClassPredicate cp | + this = TThisParam(cp) and + result = cp.getLocation() + ) + or + exists(Predicate p | + this = TResultParam(p) and + result = p.getLocation() + ) + or + exists(VarDecl v | + this = TVarParam(v, _, _) and + result = v.getLocation() + ) + } +} + +/** + * A node in a data flow graph. + * + * A node can be either an expression, a parameter, or an uninitialized local + * variable. Such nodes are created with `DataFlow::exprNode`, + * `DataFlow::parameterNode`, and `DataFlow::uninitializedNode` respectively. + */ +class Node extends TNode { + /** Gets the function to which this node belongs. */ + DataFlowCallable getFunction() { none() } // overridden in subclasses + + /** + * INTERNAL: Do not use. Alternative name for `getFunction`. + */ + final DataFlowCallable getEnclosingCallable() { result = this.getFunction() } + + /** Gets the type of this node. */ + Type getType() { none() } // overridden in subclasses + + /** + * Gets the expression corresponding to this node, if any. This predicate + * only has a result on nodes that represent the value of evaluating the + * expression. For data flowing _out of_ an expression, like when an + * argument is passed by reference, use `asDefiningArgument` instead of + * `asExpr`. + */ + Expr asExpr() { result = this.(ExprNode).getExpr() } + + /** Gets the parameter corresponding to this node, if any. */ + Parameter asParameter() { result = this.(ParameterNode).getParameter() } + + /** Gets a textual representation of this element. */ + string toString() { none() } // overridden by subclasses + + /** Gets the location of this element. */ + Location getLocation() { none() } // overridden by subclasses + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** + * Gets an upper bound on the type of this node. + */ + Type getTypeBound() { result = getType() } +} + +NewTypeBranch getNewTypeBranch(Expr e) { e.getParent*() = result.getBody() } + +/** + * An expression, viewed as a node in a data flow graph. + */ +class ExprNode extends Node, TExprNode { + Expr expr; + + ExprNode() { this = TExprNode(expr) } + + override DataFlowCallable getFunction() { + result.asPredicate() = expr.getEnclosingPredicate() + or + result.asNewTypeBranch() = getNewTypeBranch(expr) + or + not exists(expr.getEnclosingPredicate()) and + not exists(getNewTypeBranch(expr)) and + result.asTopLevel() + } + + override Type getType() { result = expr.getType() } + + override string toString() { result = expr.toString() } + + override Location getLocation() { result = expr.getLocation() } + + /** Gets the expression corresponding to this node. */ + Expr getExpr() { result = expr } +} + +ExprNode exprNode(Expr e) { result.getExpr() = e } + +class ParameterNode extends Node, TParameterNode { + Parameter p; + + ParameterNode() { this = TParameterNode(p) } + + Parameter getParameter() { result = p } + + /** + * Holds if this node is the parameter of `c` at the specified (zero-based) + * position. The implicit `this` parameter is considered to have index `-1`. + */ + predicate isParameterOf(DataFlowCallable pred, int i) { + p.getPredicate() = pred.asPredicate() and + p.getIndex() = i + } + + override DataFlowCallable getFunction() { result.asPredicate() = p.getPredicate() } + + override Type getType() { result = p.getType() } + + override string toString() { result = p.toString() } + + override Location getLocation() { result = p.getLocation() } +} + +/** A data flow node that represents a returned value in the called function. */ +abstract class ReturnNode extends Node { + /** Gets the kind of this returned value. */ + abstract ReturnKind getKind(); +} + +newtype TReturnKind = + TNormalReturnKind() or + TParameterOutKind(int i) { any(Parameter p).getIndex() = i } + +/** + * A return kind. A return kind describes how a value can be returned + * from a callable. For C++, this is simply a function return. + */ +class ReturnKind extends TReturnKind { + /** Gets a textual representation of this return kind. */ + string toString() { + this instanceof TNormalReturnKind and + result = "return" + or + exists(int i | + this = TParameterOutKind(i) and + result = "out(" + i + ")" + ) + } +} + +/** A data flow node that represents the output of a call at the call site. */ +abstract class OutNode extends Node { + /** Gets the underlying call. */ + abstract Call getCall(); +} + +class ArgumentOutNode extends Node, TArgumentOutNode, OutNode { + VarAccess acc; + Call call; + int pos; + + ArgumentOutNode() { this = TArgumentOutNode(acc, call, pos) } + + override DataFlowCallable getFunction() { + result.asPredicate() = acc.getEnclosingPredicate() + or + result.asNewTypeBranch() = getNewTypeBranch(acc) + or + not exists(acc.getEnclosingPredicate()) and + not exists(getNewTypeBranch(acc)) and + result.asTopLevel() + } + + VarAccess getVarAccess() { result = acc } + + override Type getType() { result = acc.getType() } + + override string toString() { result = acc.toString() + " [out]" } + + override Location getLocation() { result = acc.getLocation() } + + override Call getCall() { result = call } + + int getIndex() { result = pos } +} + +class OutParameterNode extends Node, ReturnNode, TOutParameterNode { + Parameter p; + + OutParameterNode() { this = TOutParameterNode(p) } + + Parameter getParameter() { result = p } + + /** + * Holds if this node is the parameter of `c` at the specified (zero-based) + * position. The implicit `this` parameter is considered to have index `-1`. + */ + predicate isParameterOf(DataFlowCallable pred, int i) { + p.getPredicate() = pred.asPredicate() and + p.getIndex() = i + } + + override DataFlowCallable getFunction() { result.asPredicate() = p.getPredicate() } + + override Type getType() { result = p.getType() } + + override string toString() { result = p.toString() } + + override Location getLocation() { result = p.getLocation() } + + override ReturnKind getKind() { result = TParameterOutKind(p.getIndex()) } +} + +/** + * A node associated with an object after an operation that might have + * changed its state. + * + * This can be either the argument to a callable after the callable returns + * (which might have mutated the argument), or the qualifier of a field after + * an update to the field. + * + * Nodes corresponding to AST elements, for example `ExprNode`, usually refer + * to the value before the update with the exception of `ClassInstanceExpr`, + * which represents the value after the constructor has run. + */ +abstract class PostUpdateNode extends Node { + /** + * Gets the node before the state update. + */ + abstract Node getPreUpdateNode(); + + override DataFlowCallable getFunction() { result = getPreUpdateNode().getFunction() } + + override Type getType() { result = getPreUpdateNode().getType() } + + override Location getLocation() { result = getPreUpdateNode().getLocation() } +} + +/** + * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local + * (intra-procedural) step. + */ +cached +predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFrom, nodeTo) } + +AstNode getParentOfExpr(Expr e) { result = e.getParent() } + +Formula getEnclosing(Expr e) { result = getParentOfExpr+(e) } + +Formula enlargeScopeStep(Formula f) { result.(Conjunction).getAnOperand() = f } + +Formula enlargeScope(Formula f) { + result = enlargeScopeStep*(f) and not exists(enlargeScopeStep(result)) +} + +predicate varaccesValue(VarAccess va, VarDecl v, Formula scope) { + va.getDeclaration() = v and + scope = enlargeScope(getEnclosing(va)) +} + +predicate thisValue(ThisAccess ta, Formula scope) { scope = enlargeScope(getEnclosing(ta)) } + +predicate resultValue(ResultAccess ra, Formula scope) { scope = enlargeScope(getEnclosing(ra)) } + +Formula getParentFormula(Formula f) { f.getParent() = result } + +predicate valueStep(Expr e1, Expr e2) { + exists(VarDecl v, Formula scope | + varaccesValue(e1, v, scope) and + varaccesValue(e2, v, scope) + ) + or + exists(VarDecl v, Formula f, Select sel | + getParentFormula*(f) = sel.getWhere() and + varaccesValue(e1, v, f) and + sel.getExpr(_) = e2 + ) + or + exists(Formula scope | + thisValue(e1, scope) and + thisValue(e2, scope) + or + resultValue(e1, scope) and + resultValue(e2, scope) + ) + or + exists(InlineCast c | + e1 = c and e2 = c.getBase() + or + e2 = c and e1 = c.getBase() + ) + or + exists(ComparisonFormula eq | + eq.getSymbol() = "=" and + eq.getAnOperand() = e1 and + eq.getAnOperand() = e2 and + e1 != e2 + ) +} + +predicate paramStep(Expr e1, Parameter p2) { + exists(VarDecl v | + p2 = TVarParam(v, _, _) and + varaccesValue(e1, v, _) + ) + or + exists(Formula scope | + p2 = TThisParam(scope.getEnclosingPredicate()) and + thisValue(e1, scope) + or + p2 = TResultParam(scope.getEnclosingPredicate()) and + resultValue(e1, scope) + ) +} + +/** + * INTERNAL: do not use. + * + * This is the local flow predicate that's used as a building block in global + * data flow. It may have less flow than the `localFlowStep` predicate. + */ +predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { + valueStep(nodeFrom.asExpr(), nodeTo.asExpr()) + or + paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter()) + or + valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr()) +} + +predicate foo(Node nodeFrom, Node nodeTo, Select sel) { + valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr()) and + sel.getExpr(_) = nodeTo.asExpr() and + nodeFrom.getLocation().getFile().getBaseName() = "OverflowStatic.ql" and + nodeFrom.getLocation().getStartLine() = 147 + // paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter()) and + // nodeFrom.getLocation().getStartLine() = 60 and + // nodeFrom.getLocation().getFile().getBaseName() = "OverflowStatic.ql" +} + +/** + * Holds if data flows from `source` to `sink` in zero or more local + * (intra-procedural) steps. + */ +predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) } + +private newtype TContent = + TFieldContent() or + TCollectionContent() or + TArrayContent() + +/** + * A description of the way data may be stored inside an object. Examples + * include instance fields, the contents of a collection object, or the contents + * of an array. + */ +class Content extends TContent { + /** Gets a textual representation of this element. */ + abstract string toString(); + + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 + } +} + +/** A reference through an instance field. */ +class FieldContent extends Content, TFieldContent { + override string toString() { result = "" } +} + +/** A reference through an array. */ +private class ArrayContent extends Content, TArrayContent { + override string toString() { result = "[]" } +} + +/** A reference through the contents of some collection-like container. */ +private class CollectionContent extends Content, TCollectionContent { + override string toString() { result = "" } +} + +class GuardCondition extends Formula { + GuardCondition() { any(IfFormula ifFormula).getCondition() = this } +} + +/** + * A guard that validates some expression. + * + * To use this in a configuration, extend the class and provide a + * characteristic predicate precisely specifying the guard, and override + * `checks` to specify what is being validated and in which branch. + * + * It is important that all extending classes in scope are disjoint. + */ +class BarrierGuard extends GuardCondition { + /** Override this predicate to hold if this guard validates `e` upon evaluating to `b`. */ + abstract predicate checks(Expr e, boolean b); + + /** Gets a node guarded by this guard. */ + final ExprNode getAGuardedNode() { none() } +} From c9f80b10529a50a6d60fa0cc9ed8393848d87074 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 15 Oct 2021 11:57:55 +0100 Subject: [PATCH 35/43] QL: Add query for using toString in query logic. --- ql/src/queries/style/ToStringInQueryLogic.ql | 88 ++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 ql/src/queries/style/ToStringInQueryLogic.ql diff --git a/ql/src/queries/style/ToStringInQueryLogic.ql b/ql/src/queries/style/ToStringInQueryLogic.ql new file mode 100644 index 00000000000..33558105e53 --- /dev/null +++ b/ql/src/queries/style/ToStringInQueryLogic.ql @@ -0,0 +1,88 @@ +/** + * @name Using 'toString' in query logic + * @description A query should not depend on the output of 'toString'. + * @kind problem + * @problem.severity error + * @id ql/to-string-in-logic + * @precision medium + * @tags maintainability + */ + +import ql +import codeql_ql.ast.internal.Builtins +import codeql.dataflow.DataFlow + +class ToString extends Predicate { + ToString() { this.getName() = "toString" } +} + +class ToStringCall extends Call { + ToStringCall() { this.getTarget() instanceof ToString } +} + +class NodesPredicate extends Predicate { + NodesPredicate() { this.getName() = "nodes" } +} + +class EdgesPredicate extends Predicate { + EdgesPredicate() { this.getName() = "edges" } +} + +class RegexpReplaceAll extends BuiltinPredicate { + RegexpReplaceAll() { this.getName() = "regexpReplaceAll" } +} + +class RegexpReplaceAllCall extends MemberCall { + RegexpReplaceAllCall() { this.getTarget() instanceof RegexpReplaceAll } +} + +class ToSelectConf extends DataFlow::Configuration { + ToSelectConf() { this = "ToSelectConf" } + + override predicate isSource(DataFlow::Node source) { + exists(ToStringCall toString | + source.asExpr() = toString and + not toString.getEnclosingPredicate() instanceof ToString + ) + } + + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(Select s).getExpr(_) or + sink.getEnclosingCallable().asPredicate() instanceof NodesPredicate or + sink.getEnclosingCallable().asPredicate() instanceof EdgesPredicate + } + + override predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + nodeFrom.getType() instanceof StringClass and + nodeTo.getType() instanceof StringClass and + exists(BinOpExpr binop | + nodeFrom.asExpr() = binop.getAnOperand() and + nodeTo.asExpr() = binop + ) + or + nodeTo.asExpr().(RegexpReplaceAllCall).getBase() = nodeFrom.asExpr() + } +} + +predicate flowsToSelect(Expr e) { + exists(DataFlow::Node source | + source.asExpr() = e and + any(ToSelectConf conf).hasFlow(source, _) + ) +} + +from ToStringCall call +where + // The call doesn't flow to a select + not flowsToSelect(call) and + // It's not part of a toString call + not call.getEnclosingPredicate() instanceof ToString and + // It's in a query + call.getLocation().getFile().getBaseName().matches("%.ql") and + // ... and not in a test + not call.getLocation() + .getFile() + .getAbsolutePath() + .toLowerCase() + .matches(["%test%", "%consistency%", "%meta%"]) +select call, "Query logic depends on implementation of 'toString'." From ccaef199bfa94b410c80a851c6f3e5f4a6c63dbd Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 10:58:38 +0000 Subject: [PATCH 36/43] Ignore overridden predicates in consistency check --- ql/src/codeql_ql/ast/internal/Predicate.qll | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ql/src/codeql_ql/ast/internal/Predicate.qll b/ql/src/codeql_ql/ast/internal/Predicate.qll index 9279b93a4ea..c9a366519ee 100644 --- a/ql/src/codeql_ql/ast/internal/Predicate.qll +++ b/ql/src/codeql_ql/ast/internal/Predicate.qll @@ -287,7 +287,9 @@ module PredConsistency { strictcount(PredicateOrBuiltin p0 | resolveCall(call, p0) and // aliases are expected to resolve to multiple. - not exists(p0.getDeclaration().(ClasslessPredicate).getAlias()) + not exists(p0.getDeclaration().(ClasslessPredicate).getAlias()) and + // overridden predicates may have multiple targets + not p0.getDeclaration().(ClassPredicate).isOverride() ) and c > 1 and resolveCall(call, p) From e185382c41f0198d1364e93a587358b9e17f898f Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 11:06:53 +0000 Subject: [PATCH 37/43] Update `bleeding-codeql-analysis.yml` --- .github/workflows/bleeding-codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bleeding-codeql-analysis.yml b/.github/workflows/bleeding-codeql-analysis.yml index b9b45afd0f0..115b145fc42 100644 --- a/.github/workflows/bleeding-codeql-analysis.yml +++ b/.github/workflows/bleeding-codeql-analysis.yml @@ -62,7 +62,7 @@ jobs: - name: Release build run: cargo build --release - name: Generate dbscheme - run: target/release/ql-generator + run: target/release/ql-generator --dbscheme ql/src/ql.dbscheme --library ql/src/codeql_ql/ast/internal/TreeSitter.qll - uses: actions/upload-artifact@v2 with: name: ql.dbscheme From 10aeadb889f2f89ac93140a5b19b2e3344e41eb8 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 11:12:52 +0000 Subject: [PATCH 38/43] Fix bad merge --- ql/src/codeql_ql/ast/internal/Predicate.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/codeql_ql/ast/internal/Predicate.qll b/ql/src/codeql_ql/ast/internal/Predicate.qll index 15b7460f2a0..1e8d27ae68d 100644 --- a/ql/src/codeql_ql/ast/internal/Predicate.qll +++ b/ql/src/codeql_ql/ast/internal/Predicate.qll @@ -152,7 +152,7 @@ module PredConsistency { strictcount(PredicateOrBuiltin p0 | resolveCall(call, p0) and // aliases are expected to resolve to multiple. - not exists(p0.(ClasslessPredicate).getAlias()) + not exists(p0.(ClasslessPredicate).getAlias()) and // overridden predicates may have multiple targets not p0.(ClassPredicate).isOverride() ) and From 1a79b13bdc93a2be2d51350544e9edeba125af94 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 15 Oct 2021 13:31:35 +0200 Subject: [PATCH 39/43] fix performance --- ql/src/codeql_ql/ast/Ast.qll | 5 ++++- ql/src/codeql_ql/ast/internal/Module.qll | 16 +++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index d8cba60276c..81a020b8af1 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -70,7 +70,10 @@ class AstNode extends TAstNode { predicate hasAnnotation(string name) { this.getAnAnnotation().getName() = name } /** Gets an annotation of this AST node. */ - Annotation getAnAnnotation() { toQL(this).getParent() = toQL(result).getParent() } + cached + Annotation getAnAnnotation() { + toQL(this).getParent() = pragma[only_bind_out](toQL(result)).getParent() + } /** * Gets the predicate that contains this AST node. diff --git a/ql/src/codeql_ql/ast/internal/Module.qll b/ql/src/codeql_ql/ast/internal/Module.qll index e5edc0ee875..7ca7abaecbc 100644 --- a/ql/src/codeql_ql/ast/internal/Module.qll +++ b/ql/src/codeql_ql/ast/internal/Module.qll @@ -148,14 +148,16 @@ private predicate resolveSelectionName(Import imp, ContainerOrModule m, int i) { cached private module Cached { - // TODO: Use `AstNode::getParent` once it is total - private QL::AstNode parent(QL::AstNode n) { - result = n.getParent() and - not n instanceof QL::Module - } - private Module getEnclosingModule0(AstNode n) { - AstNodes::toQL(result) = parent*(AstNodes::toQL(n).getParent()) + not n instanceof Module and + ( + n = result.getAChild(_) + or + exists(AstNode prev | + result = getEnclosingModule0(prev) and + n = prev.getAChild(_) + ) + ) } cached From b0bbbc54d0e54af28ab2628693b2e88034945456 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 15 Oct 2021 13:35:25 +0200 Subject: [PATCH 40/43] New query: Don't use library annotation. --- ql/src/queries/style/LibraryAnnotation.ql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 ql/src/queries/style/LibraryAnnotation.ql diff --git a/ql/src/queries/style/LibraryAnnotation.ql b/ql/src/queries/style/LibraryAnnotation.ql new file mode 100644 index 00000000000..cf4a4bc8232 --- /dev/null +++ b/ql/src/queries/style/LibraryAnnotation.ql @@ -0,0 +1,15 @@ +/** + * @name Use of deprecated annotation + * @description The library annotation is deprecated + * @kind problem + * @problem.severity warning + * @id ql/deprecated-annotation + * @tags maintainability + * @precision very-high + */ + +import ql + +from AstNode n +where n.hasAnnotation("library") and not n.hasAnnotation("deprecated") +select n, "Don't use the library annotation." From 30717310e7ca7ae1796b875f4aae70921483438b Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 15 Oct 2021 12:32:36 +0100 Subject: [PATCH 41/43] Remove the dataflow library. --- ql/src/codeql/dataflow/DataFlow.qll | 24 - .../dataflow/internal/DataFlowDispatch.qll | 19 - .../codeql/dataflow/internal/DataFlowImpl.qll | 4559 ----------------- .../dataflow/internal/DataFlowImplCommon.qll | 1294 ----- .../internal/DataFlowImplSpecific.qll | 11 - .../dataflow/internal/DataFlowPrivate.qll | 171 - .../codeql/dataflow/internal/DataFlowUtil.qll | 513 -- ql/src/queries/style/ToStringInQueryLogic.ql | 266 +- 8 files changed, 251 insertions(+), 6606 deletions(-) delete mode 100644 ql/src/codeql/dataflow/DataFlow.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowDispatch.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowImpl.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowPrivate.qll delete mode 100644 ql/src/codeql/dataflow/internal/DataFlowUtil.qll diff --git a/ql/src/codeql/dataflow/DataFlow.qll b/ql/src/codeql/dataflow/DataFlow.qll deleted file mode 100644 index 6985e801c52..00000000000 --- a/ql/src/codeql/dataflow/DataFlow.qll +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Provides a library for local (intra-procedural) and global (inter-procedural) - * data flow analysis: deciding whether data can flow from a _source_ to a - * _sink_. - * - * Unless configured otherwise, _flow_ means that the exact value of - * the source may reach the sink. We do not track flow across pointer - * dereferences or array indexing. To track these types of flow, where the - * exact value may not be preserved, import - * `semmle.code.cpp.dataflow.TaintTracking`. - * - * To use global (interprocedural) data flow, extend the class - * `DataFlow::Configuration` as documented on that class. To use local - * (intraprocedural) data flow between expressions, call - * `DataFlow::localExprFlow`. For more general cases of local data flow, call - * `DataFlow::localFlow` or `DataFlow::localFlowStep` with arguments of type - * `DataFlow::Node`. - */ - -import ql - -module DataFlow { - import internal.DataFlowImpl -} diff --git a/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll b/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll deleted file mode 100644 index f35f7f66f9e..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowDispatch.qll +++ /dev/null @@ -1,19 +0,0 @@ -private import ql -private import DataFlowUtil - -/** - * Gets a function that might be called by `call`. - */ -DataFlowCallable viableCallable(Call call) { result.asPredicate() = call.getTarget() } - -/** - * Holds if the set of viable implementations that can be called by `call` - * might be improved by knowing the call context. - */ -predicate mayBenefitFromCallContext(Call call, DataFlowCallable f) { none() } - -/** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference. - */ -DataFlowCallable viableImplInCallContext(Call call, Call ctx) { none() } diff --git a/ql/src/codeql/dataflow/internal/DataFlowImpl.qll b/ql/src/codeql/dataflow/internal/DataFlowImpl.qll deleted file mode 100644 index 4ca06c93362..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowImpl.qll +++ /dev/null @@ -1,4559 +0,0 @@ -/** - * Provides an implementation of global (interprocedural) data flow. This file - * re-exports the local (intraprocedural) data flow analysis from - * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed - * through the `Configuration` class. This file exists in several identical - * copies, allowing queries to use multiple `Configuration` classes that depend - * on each other without introducing mutual recursion among those configurations. - */ - -private import DataFlowImplCommon -private import DataFlowImplSpecific::Private -import DataFlowImplSpecific::Public - -/** - * A configuration of interprocedural data flow analysis. This defines - * sources, sinks, and any other configurable aspect of the analysis. Each - * use of the global data flow library must define its own unique extension - * of this abstract class. To create a configuration, extend this class with - * a subclass whose characteristic predicate is a unique singleton string. - * For example, write - * - * ```ql - * class MyAnalysisConfiguration extends DataFlow::Configuration { - * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } - * // Override `isSource` and `isSink`. - * // Optionally override `isBarrier`. - * // Optionally override `isAdditionalFlowStep`. - * } - * ``` - * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and - * the edges are those data-flow steps that preserve the value of the node - * along with any additional edges defined by `isAdditionalFlowStep`. - * Specifying nodes in `isBarrier` will remove those nodes from the graph, and - * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going - * and/or out-going edges from those nodes, respectively. - * - * Then, to query whether there is flow between some `source` and `sink`, - * write - * - * ```ql - * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) - * ``` - * - * Multiple configurations can coexist, but two classes extending - * `DataFlow::Configuration` should never depend on each other. One of them - * should instead depend on a `DataFlow2::Configuration`, a - * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. - */ -abstract class Configuration extends string { - bindingset[this] - Configuration() { any() } - - /** - * Holds if `source` is a relevant data flow source. - */ - abstract predicate isSource(Node source); - - /** - * Holds if `sink` is a relevant data flow sink. - */ - abstract predicate isSink(Node sink); - - /** - * Holds if data flow through `node` is prohibited. This completely removes - * `node` from the data flow graph. - */ - predicate isBarrier(Node node) { none() } - - /** Holds if data flow into `node` is prohibited. */ - predicate isBarrierIn(Node node) { none() } - - /** Holds if data flow out of `node` is prohibited. */ - predicate isBarrierOut(Node node) { none() } - - /** Holds if data flow through nodes guarded by `guard` is prohibited. */ - predicate isBarrierGuard(BarrierGuard guard) { none() } - - /** - * Holds if the additional flow step from `node1` to `node2` must be taken - * into account in the analysis. - */ - predicate isAdditionalFlowStep(Node node1, Node node2) { none() } - - /** - * Holds if an arbitrary number of implicit read steps of content `c` may be - * taken at `node`. - */ - predicate allowImplicitRead(Node node, Content c) { none() } - - /** - * Gets the virtual dispatch branching limit when calculating field flow. - * This can be overridden to a smaller value to improve performance (a - * value of 0 disables field flow), or a larger value to get more results. - */ - int fieldFlowBranchLimit() { result = 2 } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - */ - predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } - - /** - * Holds if data may flow from `source` to `sink` for this configuration. - * - * The corresponding paths are generated from the end-points and the graph - * included in the module `PathGraph`. - */ - predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowTo(Node sink) { hasFlow(_, sink) } - - /** - * Holds if data may flow from some source to `sink` for this configuration. - */ - predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } - - /** - * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` - * measured in approximate number of interprocedural steps. - */ - int explorationLimit() { none() } - - /** - * Holds if there is a partial data flow path from `source` to `node`. The - * approximate distance between `node` and the closest source is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards sink definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sources is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - */ - final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { - partialFlow(source, node, this) and - dist = node.getSourceDistance() - } - - /** - * Holds if there is a partial data flow path from `node` to `sink`. The - * approximate distance between `node` and the closest sink is `dist` and - * is restricted to be less than or equal to `explorationLimit()`. This - * predicate completely disregards source definitions. - * - * This predicate is intended for data-flow exploration and debugging and may - * perform poorly if the number of sinks is too big and/or the exploration - * limit is set too high without using barriers. - * - * This predicate is disabled (has no results) by default. Override - * `explorationLimit()` with a suitable number to enable this predicate. - * - * To use this in a `path-problem` query, import the module `PartialPathGraph`. - * - * Note that reverse flow has slightly lower precision than the corresponding - * forward flow, as reverse flow disregards type pruning among other features. - */ - final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { - revPartialFlow(node, sink, this) and - dist = node.getSinkDistance() - } -} - -/** - * This class exists to prevent mutual recursion between the user-overridden - * member predicates of `Configuration` and the rest of the data-flow library. - * Good performance cannot be guaranteed in the presence of such recursion, so - * it should be replaced by using more than one copy of the data flow library. - */ -abstract private class ConfigurationRecursionPrevention extends Configuration { - bindingset[this] - ConfigurationRecursionPrevention() { any() } - - override predicate hasFlow(Node source, Node sink) { - strictcount(Node n | this.isSource(n)) < 0 - or - strictcount(Node n | this.isSink(n)) < 0 - or - strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 - or - super.hasFlow(source, sink) - } -} - -private newtype TNodeEx = - TNodeNormal(Node n) or - TNodeImplicitRead(Node n, boolean hasRead) { - any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] - } - -private class NodeEx extends TNodeEx { - string toString() { - result = this.asNode().toString() - or - exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") - } - - Node asNode() { this = TNodeNormal(result) } - - predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } - - Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } - - pragma[nomagic] - private DataFlowCallable getEnclosingCallable0() { - nodeEnclosingCallable(this.projectToNode(), result) - } - - pragma[inline] - DataFlowCallable getEnclosingCallable() { - pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) - } - - pragma[nomagic] - private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } - - pragma[inline] - DataFlowType getDataFlowType() { - pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private class ArgNodeEx extends NodeEx { - ArgNodeEx() { this.asNode() instanceof ArgNode } -} - -private class ParamNodeEx extends NodeEx { - ParamNodeEx() { this.asNode() instanceof ParamNode } - - predicate isParameterOf(DataFlowCallable c, int i) { - this.asNode().(ParamNode).isParameterOf(c, i) - } - - int getPosition() { this.isParameterOf(_, result) } -} - -private class RetNodeEx extends NodeEx { - RetNodeEx() { this.asNode() instanceof ReturnNodeExt } - - ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } - - ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } -} - -private predicate inBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierIn(n) and - config.isSource(n) - ) -} - -private predicate outBarrier(NodeEx node, Configuration config) { - exists(Node n | - node.asNode() = n and - config.isBarrierOut(n) and - config.isSink(n) - ) -} - -private predicate fullBarrier(NodeEx node, Configuration config) { - exists(Node n | node.asNode() = n | - config.isBarrier(n) - or - config.isBarrierIn(n) and - not config.isSource(n) - or - config.isBarrierOut(n) and - not config.isSink(n) - or - exists(BarrierGuard g | - config.isBarrierGuard(g) and - n = g.getAGuardedNode() - ) - ) -} - -pragma[nomagic] -private predicate sourceNode(NodeEx node, Configuration config) { config.isSource(node.asNode()) } - -pragma[nomagic] -private predicate sinkNode(NodeEx node, Configuration config) { config.isSink(node.asNode()) } - -/** - * Holds if data can flow in one local step from `node1` to `node2`. - */ -private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - simpleLocalFlowStepExt(n1, n2) and - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.asNode() = n and - node2.isImplicitReadNode(n, false) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` does not jump between callables. - */ -private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(n1, n2) and - getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) - ) - or - exists(Node n | - config.allowImplicitRead(n, _) and - node1.isImplicitReadNode(n, true) and - node2.asNode() = n - ) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. - */ -private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - jumpStepCached(n1, n2) and - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) - ) -} - -/** - * Holds if the additional step from `node1` to `node2` jumps between callables. - */ -private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { - exists(Node n1, Node n2 | - node1.asNode() = n1 and - node2.asNode() = n2 and - config.isAdditionalFlowStep(n1, n2) and - getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and - not outBarrier(node1, config) and - not inBarrier(node2, config) and - not fullBarrier(node1, config) and - not fullBarrier(node2, config) - ) -} - -private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { - read(node1.asNode(), c, node2.asNode()) - or - exists(Node n | - node2.isImplicitReadNode(n, true) and - node1.isImplicitReadNode(n, _) and - config.allowImplicitRead(n, c) - ) -} - -private predicate store( - NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config -) { - store(node1.asNode(), tc, node2.asNode(), contentType) and - read(_, tc.getContent(), _, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { - viableReturnPosOut(call, pos, out.asNode()) -} - -pragma[nomagic] -private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - viableParamArg(call, p.asNode(), arg.asNode()) -} - -/** - * Holds if field flow should be used for the given configuration. - */ -private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } - -private 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(NodeEx node, Cc cc, Configuration config) { - not fullBarrier(node, config) and - ( - sourceNode(node, config) and - cc = false - or - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - localFlowStep(mid, node, config) - ) - or - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - additionalLocalFlowStep(mid, node, config) - ) - or - exists(NodeEx mid | - fwdFlow(mid, _, config) and - jumpStep(mid, node, config) and - cc = false - ) - or - exists(NodeEx mid | - fwdFlow(mid, _, config) and - additionalJumpStep(mid, node, config) and - cc = false - ) - or - // store - exists(NodeEx mid | - useFieldFlow(config) and - fwdFlow(mid, cc, config) and - store(mid, _, node, _, config) 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(NodeEx arg | - fwdFlow(arg, _, config) and - viableParamArgEx(_, 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(NodeEx node, Configuration config) { fwdFlow(node, _, config) } - - pragma[nomagic] - private predicate fwdFlowRead(Content c, NodeEx node, Cc cc, Configuration config) { - exists(NodeEx mid | - fwdFlow(mid, cc, config) and - read(mid, c, node, config) - ) - } - - /** - * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. - */ - pragma[nomagic] - private predicate fwdFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node, TypedContent tc | - not fullBarrier(node, config) and - useFieldFlow(config) and - fwdFlow(mid, _, config) and - store(mid, tc, node, _, config) and - c = tc.getContent() - ) - } - - pragma[nomagic] - private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { - exists(RetNodeEx ret | - fwdFlow(ret, cc, config) and - ret.getReturnPosition() = pos - ) - } - - pragma[nomagic] - private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { - exists(ReturnPosition pos | - fwdFlowReturnPosition(pos, cc, config) and - viableReturnPosOutEx(call, pos, out) - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { - fwdFlowOut(call, out, 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(ArgNodeEx arg | - fwdFlow(arg, cc, config) and - viableParamArgEx(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(NodeEx node, boolean toReturn, Configuration config) { - revFlow0(node, toReturn, config) and - fwdFlow(node, config) - } - - pragma[nomagic] - private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { - fwdFlow(node, config) and - sinkNode(node, config) and - toReturn = false - or - exists(NodeEx mid | - localFlowStep(node, mid, config) and - revFlow(mid, toReturn, config) - ) - or - exists(NodeEx mid | - additionalLocalFlowStep(node, mid, config) and - revFlow(mid, toReturn, config) - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, _, config) and - toReturn = false - ) - or - exists(NodeEx mid | - additionalJumpStep(node, mid, config) and - revFlow(mid, _, config) and - toReturn = false - ) - or - // store - exists(Content c | - revFlowStore(c, node, toReturn, config) and - revFlowConsCand(c, config) - ) - or - // read - exists(NodeEx mid, Content c | - read(node, c, mid, config) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - revFlow(mid, toReturn, pragma[only_bind_into](config)) - ) - or - // flow into a callable - 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(ReturnPosition pos | - revFlowOut(pos, config) and - node.(RetNodeEx).getReturnPosition() = pos and - toReturn = true - ) - } - - /** - * Holds if `c` is the target of a read in the flow covered by `revFlow`. - */ - pragma[nomagic] - private predicate revFlowConsCand(Content c, Configuration config) { - exists(NodeEx mid, NodeEx node | - fwdFlow(node, pragma[only_bind_into](config)) and - read(node, c, mid, config) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { - exists(NodeEx mid, TypedContent tc | - revFlow(mid, toReturn, pragma[only_bind_into](config)) and - fwdFlowConsCand(c, pragma[only_bind_into](config)) and - store(node, tc, mid, _, config) and - c = tc.getContent() - ) - } - - /** - * Holds if `c` is the target of both a read and a store in the flow covered - * by `revFlow`. - */ - private predicate revFlowIsReadAndStored(Content c, Configuration conf) { - revFlowConsCand(c, conf) and - revFlowStore(c, _, _, conf) - } - - pragma[nomagic] - predicate viableReturnPosOutNodeCandFwd1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config - ) { - fwdFlowReturnPosition(pos, _, config) and - viableReturnPosOutEx(call, pos, out) - } - - pragma[nomagic] - private predicate revFlowOut(ReturnPosition pos, Configuration config) { - exists(DataFlowCall call, NodeEx out | - revFlow(out, _, config) and - viableReturnPosOutNodeCandFwd1(call, pos, out, config) - ) - } - - pragma[nomagic] - predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config - ) { - viableParamArgEx(call, p, arg) and - fwdFlow(arg, config) - } - - pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config - ) { - exists(ParamNodeEx p | - revFlow(p, toReturn, config) and - viableParamArgNodeCandFwd1(call, p, arg, config) - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { - revFlowIn(call, arg, true, config) - } - - /** - * Holds if an output from `call` is reached in the flow covered by `revFlow` - * and data might flow through the target callable resulting in reverse flow - * reaching an argument of `call`. - */ - pragma[nomagic] - private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { - exists(NodeEx out | - revFlow(out, toReturn, config) and - fwdFlowOutFromArg(call, out, config) - ) - } - - pragma[nomagic] - predicate storeStepCand( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Content c | - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(node2, pragma[only_bind_into](config)) and - store(node1, tc, node2, contentType, config) and - c = tc.getContent() and - exists(ap1) - ) - } - - pragma[nomagic] - predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { - revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and - revFlow(n2, pragma[only_bind_into](config)) and - read(n1, c, n2, pragma[only_bind_into](config)) - } - - pragma[nomagic] - predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } - - predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { - revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) - } - - private predicate throughFlowNodeCand(NodeEx node, Configuration config) { - revFlow(node, true, config) and - fwdFlow(node, true, config) and - not inBarrier(node, config) and - not outBarrier(node, config) - } - - /** Holds if flow may return from `callable`. */ - pragma[nomagic] - private predicate returnFlowCallableNodeCand( - DataFlowCallable callable, ReturnKindExt kind, Configuration config - ) { - exists(RetNodeEx ret | - throughFlowNodeCand(ret, config) and - callable = ret.getEnclosingCallable() and - kind = ret.getKind() - ) - } - - /** - * Holds if flow may enter through `p` and reach a return node making `p` a - * candidate for the origin of a summary. - */ - predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { - exists(ReturnKindExt kind | - 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 - not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition() - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(ArgNodeEx arg, boolean toReturn | - revFlow(arg, toReturn, config) and - revFlowInToReturn(call, arg, config) and - revFlowIsReturned(call, toReturn, config) - ) - } - - predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, config)) and - fields = count(Content f0 | fwdFlowConsCand(f0, config)) and - conscand = -1 and - tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, config)) and - fields = count(Content f0 | revFlowConsCand(f0, config)) and - conscand = -1 and - tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) - } - /* End: Stage 1 logic. */ -} - -pragma[noinline] -private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - localFlowStep(node1, node2, config) -} - -pragma[noinline] -private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { - Stage1::revFlow(node2, config) and - additionalLocalFlowStep(node1, node2, config) -} - -pragma[nomagic] -private predicate viableReturnPosOutNodeCand1( - DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config -) { - Stage1::revFlow(out, config) and - Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, NodeEx out, Configuration config -) { - viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and - Stage1::revFlow(ret, config) and - not outBarrier(ret, config) and - not inBarrier(out, config) -} - -pragma[nomagic] -private predicate viableParamArgNodeCand1( - DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config -) { - Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and - Stage1::revFlow(arg, config) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config -) { - viableParamArgNodeCand1(call, p, arg, config) and - Stage1::revFlow(p, config) and - not outBarrier(arg, config) and - not inBarrier(p, config) -} - -/** - * Gets the amount of forward branching on the origin of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -private int branch(NodeEx n1, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) - ) -} - -/** - * Gets the amount of backward branching on the target of a cross-call path - * edge in the graph of paths between sources and sinks that ignores call - * contexts. - */ -private int join(NodeEx n2, Configuration conf) { - result = - strictcount(NodeEx n | - flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) - ) -} - -/** - * Holds if data can flow out of `call` from `ret` to `out`, either - * through a `ReturnNode` or through an argument that has been mutated, and - * that this step is part of a path from a source to a sink. The - * `allowsFieldFlow` flag indicates whether the branching is within the limit - * specified by the configuration. - */ -pragma[nomagic] -private predicate flowOutOfCallNodeCand1( - DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config -) { - flowOutOfCallNodeCand1(call, ret, out, config) and - exists(int b, int j | - b = branch(ret, config) and - j = join(out, config) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -/** - * Holds if data can flow into `call` and that this step is part of a - * path from a source to a sink. The `allowsFieldFlow` flag indicates whether - * the branching is within the limit specified by the configuration. - */ -pragma[nomagic] -private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config -) { - flowIntoCallNodeCand1(call, arg, p, config) and - exists(int b, int j | - b = branch(arg, config) and - j = join(p, config) and - if b.minimum(j) <= config.fieldFlowBranchLimit() - then allowsFieldFlow = true - else allowsFieldFlow = false - ) -} - -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(NodeEx node) { PrevStage::revFlow(node, _) and exists(result) } - - 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 = CallContext; - - class CcCall = CallContextCall; - - class CcNoCall = CallContextNoCall; - - Cc ccNone() { result instanceof CallContextAny } - - private class LocalCc = Unit; - - bindingset[call, c, outercc] - private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSiteDispatch(call, c) - then result = TSpecificCall(call) - else result = TSomeCall() - } - - bindingset[call, c, innercc] - private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } - - bindingset[node, cc, config] - private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } - - private predicate localStep( - NodeEx node1, NodeEx 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(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlow(node, _, _, apa, config) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config - ) { - flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, - pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - flowCand(node, _, config) and - sourceNode(node, config) and - cc = ccNone() and - argAp = apNone() and - ap = getApNil(node) - or - exists(NodeEx 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(NodeEx mid | - fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() 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 - fwdFlowOutNotFromArg(node, cc, argAp, ap, config) - or - exists(DataFlowCall call, Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx 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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, - Configuration config - ) { - exists(ArgNodeEx 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 fwdFlowOutNotFromArg( - NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config - ) { - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, out, allowsFieldFlow, config) and - inner = ret.getEnclosingCallable() and - ccOut = getCallContextReturn(inner, call, innercc) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg( - DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config - ) { - exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | - fwdFlow(ret, ccc, apSome(argAp), ap, config) and - flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and - ccc.matchesCall(call) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - /** - * 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(ParamNodeEx p | - fwdFlowIn(call, p, cc, _, argAp, ap, config) and - PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { - exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | - fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - fwdFlowOutFromArg(call, out, argAp0, ap, config) and - fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), - pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and - callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( - NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config - ) { - fwdFlow(node, _, _, ap, config) and - sinkNode(node, config) and - toReturn = false and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid | - localStep(node, mid, true, _, config, _) and - revFlow(mid, toReturn, returnAp, ap, config) - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, mid, false, _, config, _) and - revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and - toReturn = false and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | - revFlow(mid, toReturn, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - revFlowInNotToReturn(node, returnAp, ap, config) and - toReturn = false - or - exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(NodeEx 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 revFlowInNotToReturn( - ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, false, returnAp, ap, config) and - flowIntoCall(_, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn( - DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, true, apSome(returnAp), ap, config) and - flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil 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(RetNodeEx 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( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - store(node1, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - predicate revFlow(NodeEx 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( - ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config - ) { - revFlow(p, true, apSome(ap0), ap, config) and - c = p.getEnclosingCallable() - } - - predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { - exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | - parameterFlow(p, ap, ap0, c, config) and - c = ret.getEnclosingCallable() and - revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), - pragma[only_bind_into](config)) and - fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and - kind = ret.getKind() and - p.getPosition() = pos and - // we don't expect a parameter to return stored in itself - not kind.(ParamUpdateReturnKind).getPosition() = pos - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | - revFlow(arg, toReturn, returnAp, ap, config) and - revFlowInToReturn(call, arg, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) - } - - predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) - } - /* End: Stage 2 logic. */ -} - -pragma[nomagic] -private predicate flowOutOfCallNodeCand2( - DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config -) { - flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlow(node1, pragma[only_bind_into](config)) -} - -pragma[nomagic] -private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config -) { - flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) and - Stage2::revFlow(node1, pragma[only_bind_into](config)) -} - -private module LocalFlowBigStep { - /** - * A node where some checking is required, and hence the big-step relation - * is not allowed to step over. - */ - private class FlowCheckNode extends NodeEx { - FlowCheckNode() { - castNode(this.asNode()) or - clearsContentCached(this.asNode(), _) - } - } - - /** - * Holds if `node` can be the first node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - predicate localFlowEntry(NodeEx node, Configuration config) { - Stage2::revFlow(node, config) and - ( - sourceNode(node, config) or - jumpStep(_, node, config) or - additionalJumpStep(_, node, config) or - node instanceof ParamNodeEx or - node.asNode() instanceof OutNodeExt or - store(_, _, node, _, config) or - read(_, _, node, config) or - node instanceof FlowCheckNode - ) - } - - /** - * Holds if `node` can be the last node in a maximal subsequence of local - * flow steps in a dataflow path. - */ - private predicate localFlowExit(NodeEx node, Configuration config) { - exists(NodeEx next | Stage2::revFlow(next, config) | - jumpStep(node, next, config) or - additionalJumpStep(node, next, config) or - flowIntoCallNodeCand1(_, node, next, config) or - flowOutOfCallNodeCand1(_, node, next, config) or - store(node, _, next, _, config) or - read(node, _, next, config) - ) - or - node instanceof FlowCheckNode - or - sinkNode(node, config) - } - - pragma[noinline] - private predicate additionalLocalFlowStepNodeCand2( - NodeEx node1, NodeEx node2, Configuration config - ) { - additionalLocalFlowStepNodeCand1(node1, node2, config) and - Stage2::revFlow(node1, _, _, false, pragma[only_bind_into](config)) and - Stage2::revFlow(node2, _, _, false, pragma[only_bind_into](config)) - } - - /** - * Holds if the local path from `node1` to `node2` is a prefix of a maximal - * subsequence of local flow steps in a dataflow path. - * - * This is the transitive closure of `[additional]localFlowStep` beginning - * at `localFlowEntry`. - */ - pragma[nomagic] - private predicate localFlowStepPlus( - NodeEx node1, NodeEx node2, boolean preservesValue, DataFlowType t, Configuration config, - LocalCallContext cc - ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - ( - localFlowEntry(node1, pragma[only_bind_into](config)) and - ( - localFlowStepNodeCand1(node1, node2, config) and - preservesValue = true and - t = node1.getDataFlowType() // irrelevant dummy value - or - additionalLocalFlowStepNodeCand2(node1, node2, config) and - preservesValue = false and - t = node2.getDataFlowType() - ) and - node1 != node2 and - cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and - Stage2::revFlow(node2, pragma[only_bind_into](config)) - or - exists(NodeEx mid | - localFlowStepPlus(node1, mid, preservesValue, t, pragma[only_bind_into](config), cc) and - localFlowStepNodeCand1(mid, node2, config) and - not mid instanceof FlowCheckNode and - Stage2::revFlow(node2, pragma[only_bind_into](config)) - ) - or - exists(NodeEx mid | - localFlowStepPlus(node1, mid, _, _, pragma[only_bind_into](config), cc) and - additionalLocalFlowStepNodeCand2(mid, node2, config) and - not mid instanceof FlowCheckNode and - preservesValue = false and - t = node2.getDataFlowType() and - Stage2::revFlow(node2, pragma[only_bind_into](config)) - ) - ) - } - - /** - * Holds if `node1` can step to `node2` in one or more local steps and this - * path can occur as a maximal subsequence of local steps in a dataflow path. - */ - pragma[nomagic] - predicate localFlowBigStep( - NodeEx node1, NodeEx node2, boolean preservesValue, AccessPathFrontNil apf, - Configuration config, LocalCallContext callContext - ) { - localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and - localFlowExit(node2, config) - } -} - -private import LocalFlowBigStep - -private module Stage3 { - module PrevStage = Stage2; - - class ApApprox = PrevStage::Ap; - - class Ap = AccessPathFront; - - class ApNil = AccessPathFrontNil; - - private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } - - private ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) - } - - 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 ccNone() { result = false } - - private class LocalCc = Unit; - - bindingset[call, c, outercc] - private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } - - bindingset[call, c, innercc] - private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } - - bindingset[node, cc, config] - private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } - - private predicate localStep( - NodeEx node1, NodeEx 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; - - pragma[nomagic] - private predicate clear(NodeEx node, Ap ap) { ap.isClearedAt(node.asNode()) } - - pragma[nomagic] - private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } - - bindingset[node, ap] - private predicate filter(NodeEx node, Ap ap) { - not clear(node, ap) and - if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), 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(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlow(node, _, _, apa, config) - } - - bindingset[result, apa] - private ApApprox unbindApa(ApApprox apa) { - exists(ApApprox apa0 | - apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) - ) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config - ) { - flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, - pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - fwdFlow0(node, cc, argAp, ap, config) and - flowCand(node, unbindApa(getApprox(ap)), config) and - filter(node, ap) - } - - pragma[nomagic] - private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - flowCand(node, _, config) and - sourceNode(node, config) and - cc = ccNone() and - argAp = apNone() and - ap = getApNil(node) - or - exists(NodeEx 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(NodeEx mid | - fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() 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 - fwdFlowOutNotFromArg(node, cc, argAp, ap, config) - or - exists(DataFlowCall call, Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType | - fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, unbindApa(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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, - Configuration config - ) { - exists(ArgNodeEx 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 fwdFlowOutNotFromArg( - NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config - ) { - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, out, allowsFieldFlow, config) and - inner = ret.getEnclosingCallable() and - ccOut = getCallContextReturn(inner, call, innercc) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg( - DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config - ) { - exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | - fwdFlow(ret, ccc, apSome(argAp), ap, config) and - flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and - ccc.matchesCall(call) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - /** - * 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(ParamNodeEx p | - fwdFlowIn(call, p, cc, _, argAp, ap, config) and - PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { - exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | - fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - fwdFlowOutFromArg(call, out, argAp0, ap, config) and - fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), - pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and - callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( - NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config - ) { - fwdFlow(node, _, _, ap, config) and - sinkNode(node, config) and - toReturn = false and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid | - localStep(node, mid, true, _, config, _) and - revFlow(mid, toReturn, returnAp, ap, config) - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, mid, false, _, config, _) and - revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and - toReturn = false and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | - revFlow(mid, toReturn, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - revFlowInNotToReturn(node, returnAp, ap, config) and - toReturn = false - or - exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(NodeEx 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 revFlowInNotToReturn( - ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, false, returnAp, ap, config) and - flowIntoCall(_, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn( - DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, true, apSome(returnAp), ap, config) and - flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil 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(RetNodeEx 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( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - store(node1, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - predicate revFlow(NodeEx 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( - ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config - ) { - revFlow(p, true, apSome(ap0), ap, config) and - c = p.getEnclosingCallable() - } - - predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { - exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | - parameterFlow(p, ap, ap0, c, config) and - c = ret.getEnclosingCallable() and - revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), - pragma[only_bind_into](config)) and - fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and - kind = ret.getKind() and - p.getPosition() = pos and - // we don't expect a parameter to return stored in itself - not kind.(ParamUpdateReturnKind).getPosition() = pos - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | - revFlow(arg, toReturn, returnAp, ap, config) and - revFlowInToReturn(call, arg, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) - } - - predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) - } - /* End: Stage 3 logic. */ -} - -/** - * Holds if `argApf` is recorded as the summary context for flow reaching `node` - * and remains relevant for the following pruning stage. - */ -private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) { - exists(AccessPathFront apf | - Stage3::revFlow(node, true, _, apf, config) and - Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config) - ) -} - -/** - * Holds if a length 2 access path approximation with the head `tc` is expected - * to be expensive. - */ -private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { - exists(int tails, int nodes, int apLimit, int tupleLimit | - tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and - nodes = - strictcount(NodeEx n | - Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - or - flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) - ) and - accessPathApproxCostLimits(apLimit, tupleLimit) and - apLimit < tails and - tupleLimit < (tails - 1) * nodes and - not tc.forceHighPrecision() - ) -} - -private newtype TAccessPathApprox = - TNil(DataFlowType t) or - TConsNil(TypedContent tc, DataFlowType t) { - Stage3::consCand(tc, TFrontNil(t), _) and - not expensiveLen2unfolding(tc, _) - } or - TConsCons(TypedContent tc1, TypedContent tc2, int len) { - Stage3::consCand(tc1, TFrontHead(tc2), _) and - len in [2 .. accessPathLimit()] and - not expensiveLen2unfolding(tc1, _) - } or - TCons1(TypedContent tc, int len) { - len in [1 .. accessPathLimit()] and - expensiveLen2unfolding(tc, _) - } - -/** - * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only - * the first two elements of the list and its length are tracked. If data flows - * from a source to a given node with a given `AccessPathApprox`, this indicates - * the sequence of dereference operations needed to get from the value in the node - * to the tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPathApprox extends TAccessPathApprox { - abstract string toString(); - - abstract TypedContent getHead(); - - abstract int len(); - - abstract DataFlowType getType(); - - abstract AccessPathFront getFront(); - - /** Gets the access path obtained by popping `head` from this path, if any. */ - abstract AccessPathApprox pop(TypedContent head); -} - -private class AccessPathApproxNil extends AccessPathApprox, TNil { - private DataFlowType t; - - AccessPathApproxNil() { this = TNil(t) } - - override string toString() { result = concat(": " + ppReprType(t)) } - - override TypedContent getHead() { none() } - - override int len() { result = 0 } - - override DataFlowType getType() { result = t } - - override AccessPathFront getFront() { result = TFrontNil(t) } - - override AccessPathApprox pop(TypedContent head) { none() } -} - -abstract private class AccessPathApproxCons extends AccessPathApprox { } - -private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { - private TypedContent tc; - private DataFlowType t; - - AccessPathApproxConsNil() { this = TConsNil(tc, t) } - - override string toString() { - // The `concat` becomes "" if `ppReprType` has no result. - result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) - } - - override TypedContent getHead() { result = tc } - - override int len() { result = 1 } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } -} - -private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { - private TypedContent tc1; - private TypedContent tc2; - private int len; - - AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } - - override string toString() { - if len = 2 - then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" - else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc1 } - - override int len() { result = len } - - override DataFlowType getType() { result = tc1.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc1) } - - override AccessPathApprox pop(TypedContent head) { - head = tc1 and - ( - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - } -} - -private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { - private TypedContent tc; - private int len; - - AccessPathApproxCons1() { this = TCons1(tc, len) } - - override string toString() { - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - } - - override TypedContent getHead() { result = tc } - - override int len() { result = len } - - override DataFlowType getType() { result = tc.getContainerType() } - - override AccessPathFront getFront() { result = TFrontHead(tc) } - - override AccessPathApprox pop(TypedContent head) { - head = tc and - ( - exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | - result = TConsCons(tc2, _, len - 1) - or - len = 2 and - result = TConsNil(tc2, _) - or - result = TCons1(tc2, len - 1) - ) - or - exists(DataFlowType t | - len = 1 and - Stage3::consCand(tc, TFrontNil(t), _) and - result = TNil(t) - ) - ) - } -} - -/** Gets the access path obtained by popping `tc` from `ap`, if any. */ -private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } - -/** Gets the access path obtained by pushing `tc` onto `ap`. */ -private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } - -private newtype TAccessPathApproxOption = - TAccessPathApproxNone() or - TAccessPathApproxSome(AccessPathApprox apa) - -private class AccessPathApproxOption extends TAccessPathApproxOption { - string toString() { - this = TAccessPathApproxNone() and result = "" - or - this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) - } -} - -private module Stage4 { - module PrevStage = Stage3; - - class ApApprox = PrevStage::Ap; - - class Ap = AccessPathApprox; - - class ApNil = AccessPathApproxNil; - - private ApApprox getApprox(Ap ap) { result = ap.getFront() } - - private ApNil getApNil(NodeEx node) { - PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) - } - - 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 ccNone() { result instanceof CallContextAny } - - private class LocalCc = LocalCallContext; - - bindingset[call, c, outercc] - private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { - checkCallContextCall(outercc, call, c) and - if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() - } - - bindingset[call, c, innercc] - private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { - checkCallContextReturn(innercc, c, call) and - if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() - } - - bindingset[node, cc, config] - private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { - localFlowEntry(node, config) and - result = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - node.getEnclosingCallable()) - } - - private predicate localStep( - NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc - ) { - localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) - } - - pragma[nomagic] - private predicate flowOutOfCall( - DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config - ) { - flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and - PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) - } - - pragma[nomagic] - private predicate flowIntoCall( - DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, - Configuration config - ) { - flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and - PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and - PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) - } - - bindingset[node, ap] - private predicate filter(NodeEx 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() } - - /* Begin: Stage 4 logic. */ - private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) { - PrevStage::revFlow(node, _, _, apa, config) - } - - bindingset[result, apa] - private ApApprox unbindApa(ApApprox apa) { - exists(ApApprox apa0 | - apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) - ) - } - - pragma[nomagic] - private predicate flowThroughOutOfCall( - DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config - ) { - flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and - PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, - pragma[only_bind_into](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(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - fwdFlow0(node, cc, argAp, ap, config) and - flowCand(node, unbindApa(getApprox(ap)), config) and - filter(node, ap) - } - - pragma[nomagic] - private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { - flowCand(node, _, config) and - sourceNode(node, config) and - cc = ccNone() and - argAp = apNone() and - ap = getApNil(node) - or - exists(NodeEx 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(NodeEx mid | - fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - jumpStep(mid, node, config) and - cc = ccNone() and - argAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and - flowCand(node, _, pragma[only_bind_into](config)) and - additionalJumpStep(mid, node, config) and - cc = ccNone() 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 - fwdFlowOutNotFromArg(node, cc, argAp, ap, config) - or - exists(DataFlowCall call, Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) - } - - pragma[nomagic] - private predicate fwdFlowStore( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config - ) { - exists(DataFlowType contentType | - fwdFlow(node1, cc, argAp, ap1, config) and - PrevStage::storeStepCand(node1, unbindApa(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, NodeEx node1, NodeEx 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, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, - Configuration config - ) { - exists(ArgNodeEx 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 fwdFlowOutNotFromArg( - NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config - ) { - exists( - DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, - DataFlowCallable inner - | - fwdFlow(ret, innercc, argAp, ap, config) and - flowOutOfCall(call, ret, out, allowsFieldFlow, config) and - inner = ret.getEnclosingCallable() and - ccOut = getCallContextReturn(inner, call, innercc) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate fwdFlowOutFromArg( - DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config - ) { - exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | - fwdFlow(ret, ccc, apSome(argAp), ap, config) and - flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and - ccc.matchesCall(call) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - /** - * 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(ParamNodeEx p | - fwdFlowIn(call, p, cc, _, argAp, ap, config) and - PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) - ) - } - - pragma[nomagic] - private predicate storeStepFwd( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config - ) { - fwdFlowStore(node1, ap1, tc, node2, _, _, config) and - ap2 = apCons(tc, ap1) and - fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) - } - - private predicate readStepFwd( - NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config - ) { - fwdFlowRead(ap1, c, n1, n2, _, _, config) and - fwdFlowConsCand(ap1, c, ap2, config) - } - - pragma[nomagic] - private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { - exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | - fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, - pragma[only_bind_into](config)) and - fwdFlowOutFromArg(call, out, argAp0, ap, config) and - fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), - pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), - pragma[only_bind_into](config)) - ) - } - - pragma[nomagic] - private predicate flowThroughIntoCall( - DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config - ) { - flowIntoCall(call, arg, p, allowsFieldFlow, config) and - fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and - PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and - callMayFlowThroughFwd(call, pragma[only_bind_into](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(NodeEx 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( - NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config - ) { - fwdFlow(node, _, _, ap, config) and - sinkNode(node, config) and - toReturn = false and - returnAp = apNone() and - ap instanceof ApNil - or - exists(NodeEx mid | - localStep(node, mid, true, _, config, _) and - revFlow(mid, toReturn, returnAp, ap, config) - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - localStep(node, mid, false, _, config, _) and - revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and - ap instanceof ApNil - ) - or - exists(NodeEx mid | - jumpStep(node, mid, config) and - revFlow(mid, _, _, ap, config) and - toReturn = false and - returnAp = apNone() - ) - or - exists(NodeEx mid, ApNil nil | - fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and - additionalJumpStep(node, mid, config) and - revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](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(NodeEx mid, Ap ap0 | - revFlow(mid, toReturn, returnAp, ap0, config) and - readStepFwd(node, ap, _, mid, ap0, config) - ) - or - // flow into a callable - revFlowInNotToReturn(node, returnAp, ap, config) and - toReturn = false - or - exists(DataFlowCall call, 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, NodeEx node, TypedContent tc, NodeEx 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(NodeEx mid, Ap tail0 | - revFlow(mid, _, _, tail, config) and - tail = pragma[only_bind_into](tail0) and - readStepFwd(_, cons, c, mid, tail0, config) - ) - } - - pragma[nomagic] - private predicate revFlowOut( - DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(NodeEx 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 revFlowInNotToReturn( - ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, false, returnAp, ap, config) and - flowIntoCall(_, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil or allowsFieldFlow = true - ) - } - - pragma[nomagic] - private predicate revFlowInToReturn( - DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config - ) { - exists(ParamNodeEx p, boolean allowsFieldFlow | - revFlow(p, true, apSome(returnAp), ap, config) and - flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) - | - ap instanceof ApNil 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(RetNodeEx 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( - NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, - Configuration config - ) { - exists(Ap ap2, Content c | - store(node1, tc, node2, contentType, config) and - revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and - revFlowConsCand(ap2, c, ap1, config) - ) - } - - predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { - exists(Ap ap1, Ap ap2 | - revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and - readStepFwd(node1, ap1, c, node2, ap2, config) and - revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, - pragma[only_bind_into](config)) - ) - } - - predicate revFlow(NodeEx 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( - ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config - ) { - revFlow(p, true, apSome(ap0), ap, config) and - c = p.getEnclosingCallable() - } - - predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { - exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | - parameterFlow(p, ap, ap0, c, config) and - c = ret.getEnclosingCallable() and - revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), - pragma[only_bind_into](config)) and - fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and - kind = ret.getKind() and - p.getPosition() = pos and - // we don't expect a parameter to return stored in itself - not kind.(ParamUpdateReturnKind).getPosition() = pos - ) - } - - pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { - exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | - revFlow(arg, toReturn, returnAp, ap, config) and - revFlowInToReturn(call, arg, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) - } - - predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { - fwd = true and - nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and - tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) - or - fwd = false and - nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and - fields = count(TypedContent f0 | consCand(f0, _, config)) and - conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and - tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) - } - /* End: Stage 4 logic. */ -} - -bindingset[conf, result] -private Configuration unbindConf(Configuration conf) { - exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) -} - -private predicate nodeMayUseSummary(NodeEx n, AccessPathApprox apa, Configuration config) { - exists(DataFlowCallable c, AccessPathApprox apa0 | - 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(ParamNodeEx p, AccessPath ap) { - Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) - } - -/** - * A context for generating flow summaries. This represents flow entry through - * a specific parameter with an access path of a specific shape. - * - * Summaries are only created for parameters that may flow through. - */ -abstract private class SummaryCtx extends TSummaryCtx { - abstract string toString(); -} - -/** A summary context from which no flow summary can be generated. */ -private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { - override string toString() { result = "" } -} - -/** A summary context from which a flow summary can be generated. */ -private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParamNodeEx p; - private AccessPath ap; - - SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } - - int getParameterPos() { p.isParameterOf(_, result) } - - override string toString() { result = p + ": " + ap } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * Gets the number of length 2 access path approximations that correspond to `apa`. - */ -private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { - exists(TypedContent tc, int len | - tc = apa.getHead() and - len = apa.len() and - result = - strictcount(AccessPathFront apf | - Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), - config) - ) - ) -} - -private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { - result = - strictcount(NodeEx n | - Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config) - ) -} - -/** - * Holds if a length 2 access path approximation matching `apa` is expected - * to be expensive. - */ -private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = count1to2unfold(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - apLimit < aps and - tupleLimit < (aps - 1) * nodes - ) -} - -private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { - exists(TypedContent head | - apa.pop(head) = result and - Stage4::consCand(head, result, config) - ) -} - -/** - * Holds with `unfold = false` if a precise head-tail representation of `apa` is - * expected to be expensive. Holds with `unfold = true` otherwise. - */ -private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { - if apa.getHead().forceHighPrecision() - then unfold = true - else - exists(int aps, int nodes, int apLimit, int tupleLimit | - aps = countPotentialAps(apa, config) and - nodes = countNodesUsingAccessPath(apa, config) and - accessPathCostLimits(apLimit, tupleLimit) and - if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true - ) -} - -/** - * Gets the number of `AccessPath`s that correspond to `apa`. - */ -private int countAps(AccessPathApprox apa, Configuration config) { - evalUnfold(apa, false, config) and - result = 1 and - (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) - or - evalUnfold(apa, false, config) and - result = count1to2unfold(apa, config) and - not expensiveLen1to2unfolding(apa, config) - or - evalUnfold(apa, true, config) and - result = countPotentialAps(apa, config) -} - -/** - * Gets the number of `AccessPath`s that would correspond to `apa` assuming - * that it is expanded to a precise head-tail representation. - */ -language[monotonicAggregates] -private int countPotentialAps(AccessPathApprox apa, Configuration config) { - apa instanceof AccessPathApproxNil and result = 1 - or - result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) -} - -private newtype TAccessPath = - TAccessPathNil(DataFlowType t) or - TAccessPathCons(TypedContent head, AccessPath tail) { - exists(AccessPathApproxCons apa | - not evalUnfold(apa, false, _) and - head = apa.getHead() and - tail.getApprox() = getATail(apa, _) - ) - } or - TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - not expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head1 = apa.getHead() and - head2 = getATail(apa, _).getHead() - ) - } or - TAccessPathCons1(TypedContent head, int len) { - exists(AccessPathApproxCons apa | - evalUnfold(apa, false, _) and - expensiveLen1to2unfolding(apa, _) and - apa.len() = len and - head = apa.getHead() - ) - } - -private newtype TPathNode = - TPathNodeMid(NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { - // A PathNode is introduced by a source ... - Stage4::revFlow(node, config) and - sourceNode(node, config) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = TAccessPathNil(node.getDataFlowType()) - or - // ... or a step from an existing PathNode to another node. - exists(PathNodeMid mid | - pathStep(mid, node, cc, sc, ap) and - pragma[only_bind_into](config) = mid.getConfiguration() and - Stage4::revFlow(node, _, _, ap.getApprox(), pragma[only_bind_into](config)) - ) - } or - TPathNodeSink(NodeEx node, Configuration config) { - sinkNode(node, pragma[only_bind_into](config)) and - Stage4::revFlow(node, pragma[only_bind_into](config)) and - ( - // A sink that is also a source ... - sourceNode(node, config) - or - // ... or a sink that can be reached from a source - exists(PathNodeMid mid | - pathStep(mid, node, _, _, TAccessPathNil(_)) and - pragma[only_bind_into](config) = mid.getConfiguration() - ) - ) - } - -/** - * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a - * source to a given node with a given `AccessPath`, this indicates the sequence - * of dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ -abstract private class AccessPath extends TAccessPath { - /** Gets the head of this access path, if any. */ - abstract TypedContent getHead(); - - /** Gets the tail of this access path, if any. */ - abstract AccessPath getTail(); - - /** Gets the front of this access path. */ - abstract AccessPathFront getFront(); - - /** Gets the approximation of this access path. */ - abstract AccessPathApprox getApprox(); - - /** Gets the length of this access path. */ - abstract int length(); - - /** Gets a textual representation of this access path. */ - abstract string toString(); - - /** Gets the access path obtained by popping `tc` from this access path, if any. */ - final AccessPath pop(TypedContent tc) { - result = this.getTail() and - tc = this.getHead() - } - - /** Gets the access path obtained by pushing `tc` onto this access path. */ - final AccessPath push(TypedContent tc) { this = result.pop(tc) } -} - -private class AccessPathNil extends AccessPath, TAccessPathNil { - private DataFlowType t; - - AccessPathNil() { this = TAccessPathNil(t) } - - DataFlowType getType() { result = t } - - override TypedContent getHead() { none() } - - override AccessPath getTail() { none() } - - override AccessPathFrontNil getFront() { result = TFrontNil(t) } - - override AccessPathApproxNil getApprox() { result = TNil(t) } - - override int length() { result = 0 } - - override string toString() { result = concat(": " + ppReprType(t)) } -} - -private class AccessPathCons extends AccessPath, TAccessPathCons { - private TypedContent head; - private AccessPath tail; - - AccessPathCons() { this = TAccessPathCons(head, tail) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { result = tail } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { - result = TConsNil(head, tail.(AccessPathNil).getType()) - or - result = TConsCons(head, tail.getHead(), this.length()) - or - result = TCons1(head, this.length()) - } - - override int length() { result = 1 + tail.length() } - - private string toStringImpl(boolean needsSuffix) { - exists(DataFlowType t | - tail = TAccessPathNil(t) and - needsSuffix = false and - result = head.toString() + "]" + concat(" : " + ppReprType(t)) - ) - or - result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) - or - exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | - result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true - or - result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false - ) - or - exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | - result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true - or - result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false - ) - } - - override string toString() { - result = "[" + this.toStringImpl(true) + length().toString() + ")]" - or - result = "[" + this.toStringImpl(false) - } -} - -private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { - private TypedContent head1; - private TypedContent head2; - private int len; - - AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } - - override TypedContent getHead() { result = head1 } - - override AccessPath getTail() { - Stage4::consCand(head1, result.getApprox(), _) and - result.getHead() = head2 and - result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head1) } - - override AccessPathApproxCons getApprox() { - result = TConsCons(head1, head2, len) or - result = TCons1(head1, len) - } - - override int length() { result = len } - - override string toString() { - if len = 2 - then result = "[" + head1.toString() + ", " + head2.toString() + "]" - else - result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" - } -} - -private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { - private TypedContent head; - private int len; - - AccessPathCons1() { this = TAccessPathCons1(head, len) } - - override TypedContent getHead() { result = head } - - override AccessPath getTail() { - Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 - } - - override AccessPathFrontHead getFront() { result = TFrontHead(head) } - - override AccessPathApproxCons getApprox() { result = TCons1(head, len) } - - override int length() { result = len } - - override string toString() { - if len = 1 - then result = "[" + head.toString() + "]" - else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" - } -} - -/** - * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. - * Only those `PathNode`s that are reachable from a source are generated. - */ -class PathNode extends TPathNode { - /** Gets a textual representation of this element. */ - string toString() { none() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { none() } - - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - none() - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.(PathNodeImpl).getNodeEx().projectToNode() = result } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - private PathNode getASuccessorIfHidden() { - this.(PathNodeImpl).isHidden() and - result = this.(PathNodeImpl).getASuccessorImpl() - } - - /** Gets a successor of this node, if any. */ - final PathNode getASuccessor() { - result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and - not this.(PathNodeImpl).isHidden() and - not result.(PathNodeImpl).isHidden() - } - - /** Holds if this node is a source. */ - predicate isSource() { none() } -} - -abstract private class PathNodeImpl extends PathNode { - abstract PathNode getASuccessorImpl(); - - abstract NodeEx getNodeEx(); - - predicate isHidden() { - hiddenNode(this.getNodeEx().asNode()) and - not this.isSource() and - not this instanceof PathNodeSink - or - this.getNodeEx() instanceof TNodeImplicitRead - } - - private string ppAp() { - this instanceof PathNodeSink and result = "" - or - exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - this instanceof PathNodeSink and result = "" - or - result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" - } - - override string toString() { result = this.getNodeEx().toString() + ppAp() } - - override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** Holds if `n` can reach a sink. */ -private predicate directReach(PathNode n) { - n instanceof PathNodeSink or directReach(n.getASuccessor()) -} - -/** Holds if `n` can reach a sink or is used in a subpath. */ -private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) } - -/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ -private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) } - -private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) - -/** - * Provides the query predicates needed to include a graph in a path-problem query. - */ -module PathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(b) } - - /** Holds if `n` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode n, string key, string val) { - reach(n) and key = "semmle.label" and val = n.toString() - } - - query predicate subpaths = Subpaths::subpaths/4; -} - -/** - * An intermediate flow graph node. This is a triple consisting of a `Node`, - * a `CallContext`, and a `Configuration`. - */ -private class PathNodeMid extends PathNodeImpl, TPathNodeMid { - NodeEx node; - CallContext cc; - SummaryCtx sc; - AccessPath ap; - Configuration config; - - PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) } - - override NodeEx getNodeEx() { result = node } - - CallContext getCallContext() { result = cc } - - SummaryCtx getSummaryCtx() { result = sc } - - AccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - private PathNodeMid getSuccMid() { - pathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx(), - result.getAp()) and - result.getConfiguration() = unbindConf(this.getConfiguration()) - } - - override PathNodeImpl getASuccessorImpl() { - // an intermediate step to another intermediate node - result = getSuccMid() - or - // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges - exists(PathNodeMid mid, PathNodeSink sink | - mid = getSuccMid() and - mid.getNodeEx() = sink.getNodeEx() and - mid.getAp() instanceof AccessPathNil and - sink.getConfiguration() = unbindConf(mid.getConfiguration()) and - result = sink - ) - } - - override predicate isSource() { - sourceNode(node, config) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap instanceof AccessPathNil - } -} - -/** - * A flow graph node corresponding to a sink. This is disjoint from the - * intermediate nodes in order to uniquely correspond to a given sink by - * excluding the `CallContext`. - */ -private class PathNodeSink extends PathNodeImpl, TPathNodeSink { - NodeEx node; - Configuration config; - - PathNodeSink() { this = TPathNodeSink(node, config) } - - override NodeEx getNodeEx() { result = node } - - override Configuration getConfiguration() { result = config } - - override PathNode getASuccessorImpl() { none() } - - override predicate isSource() { sourceNode(node, config) } -} - -/** - * Holds if data may flow from `mid` to `node`. The last step in or out of - * a callable is recorded by `cc`. - */ -private predicate pathStep( - PathNodeMid mid, NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap -) { - exists(AccessPath ap0, NodeEx midnode, Configuration conf, LocalCallContext localCC | - midnode = mid.getNodeEx() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = - getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), - midnode.getEnclosingCallable()) and - ap0 = mid.getAp() - | - localFlowBigStep(midnode, node, true, _, conf, localCC) and - ap = ap0 - or - localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and - ap0 instanceof AccessPathNil - ) - or - jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - ap = mid.getAp() - or - additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and - cc instanceof CallContextAny and - sc instanceof SummaryCtxNone and - mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(node.getDataFlowType()) - or - exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and - sc = mid.getSummaryCtx() - or - pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp() - or - pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone - or - pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() -} - -pragma[nomagic] -private predicate pathReadStep( - PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - tc = ap0.getHead() and - Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and - cc = mid.getCallContext() -} - -pragma[nomagic] -private predicate pathStoreStep( - PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc -) { - ap0 = mid.getAp() and - Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and - cc = mid.getCallContext() -} - -private predicate pathOutOfCallable0( - PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa, - Configuration config -) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - apa = mid.getAp().getApprox() and - config = mid.getConfiguration() -} - -pragma[nomagic] -private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa, - Configuration config -) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - pathOutOfCallable0(mid, pos, innercc, apa, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) -} - -pragma[noinline] -private NodeEx getAnOutNodeFlow( - ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config -) { - result.asNode() = kind.getAnOutNode(call) and - Stage4::revFlow(result, _, _, apa, config) -} - -/** - * Holds if data may flow from `mid` to `out`. The last step of this path - * is a return from a callable and is recorded by `cc`, if needed. - */ -pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc) { - exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | - pathOutOfCallable1(mid, call, kind, cc, apa, config) and - out = getAnOutNodeFlow(kind, call, apa, config) - ) -} - -/** - * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. - */ -pragma[noinline] -private predicate pathIntoArg( - PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa -) { - exists(ArgNode arg | - arg = mid.getNodeEx().asNode() and - cc = mid.getCallContext() and - arg.argumentOf(call, i) and - ap = mid.getAp() and - apa = ap.getApprox() - ) -} - -pragma[noinline] -private predicate parameterCand( - DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config -) { - exists(ParamNodeEx p | - Stage4::revFlow(p, _, _, apa, config) and - p.isParameterOf(callable, i) - ) -} - -pragma[nomagic] -private predicate pathIntoCallable0( - PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, - AccessPath ap -) { - exists(AccessPathApprox apa | - pathIntoArg(mid, i, outercc, call, ap, apa) and - callable = resolveCall(call, outercc) and - parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration()) - ) -} - -/** - * Holds if data may flow from `mid` to `p` through `call`. The contexts - * before and after entering the callable are `outercc` and `innercc`, - * respectively. - */ -private predicate pathIntoCallable( - PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, - DataFlowCall call -) { - exists(int i, DataFlowCallable callable, AccessPath ap | - pathIntoCallable0(mid, callable, i, outercc, call, ap) and - p.isParameterOf(callable, i) and - ( - sc = TSummaryCtxSome(p, ap) - or - not exists(TSummaryCtxSome(p, ap)) and - sc = TSummaryCtxNone() - ) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) -} - -/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ -pragma[nomagic] -private predicate paramFlowsThrough( - ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa, - Configuration config -) { - exists(PathNodeMid mid, RetNodeEx ret, int pos | - mid.getNodeEx() = ret and - kind = ret.getKind() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - config = mid.getConfiguration() and - ap = mid.getAp() and - apa = ap.getApprox() and - pos = sc.getParameterPos() and - not kind.(ParamUpdateReturnKind).getPosition() = pos - ) -} - -pragma[nomagic] -private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap, - AccessPathApprox apa -) { - exists(CallContext innercc, SummaryCtx sc | - pathIntoCallable(mid, _, cc, innercc, sc, call) and - paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration())) - ) -} - -/** - * Holds if data may flow from `mid` through a callable to the node `out`. - * The context `cc` is restored to its value prior to entering the callable. - */ -pragma[noinline] -private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) { - exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | - pathThroughCallable0(call, mid, kind, cc, ap, apa) and - out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration())) - ) -} - -private module Subpaths { - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths01( - PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, AccessPath apout - ) { - pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and - pathIntoCallable(arg, par, _, innercc, sc, _) and - paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, - unbindConf(arg.getConfiguration())) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by - * `kind`, `sc`, `apout`, and `innercc`. - */ - pragma[nomagic] - private predicate subpaths02( - PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, - NodeEx out, AccessPath apout - ) { - subpaths01(arg, par, sc, innercc, kind, out, apout) and - out.asNode() = kind.getAnOutNode(_) - } - - pragma[nomagic] - private Configuration getPathNodeConf(PathNode n) { result = n.getConfiguration() } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple. - */ - pragma[nomagic] - private predicate subpaths03( - PathNode arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, AccessPath apout - ) { - exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | - subpaths02(arg, par, sc, innercc, kind, out, apout) and - ret.getNodeEx() = retnode and - kind = retnode.getKind() and - innercc = ret.getCallContext() and - sc = ret.getSummaryCtx() and - ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and - apout = ret.getAp() and - not ret.isHidden() - ) - } - - /** - * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through - * a subpath between `par` and `ret` with the connecting edges `arg -> par` and - * `ret -> out` is summarized as the edge `arg -> out`. - */ - predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) { - exists(ParamNodeEx p, NodeEx o, AccessPath apout | - pragma[only_bind_into](arg).getASuccessor() = par and - pragma[only_bind_into](arg).getASuccessor() = out and - subpaths03(arg, p, ret, o, apout) and - par.getNodeEx() = p and - out.getNodeEx() = o and - out.getAp() = apout - ) - } - - /** - * Holds if `n` can reach a return node in a summarized subpath. - */ - predicate retReach(PathNode n) { - subpaths(_, _, n, _) - or - exists(PathNode mid | - retReach(mid) and - n.getASuccessor() = mid and - not subpaths(_, mid, _, _) - ) - } -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -private predicate flowsTo( - PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration -) { - flowsource.isSource() and - flowsource.getConfiguration() = configuration and - flowsource.(PathNodeImpl).getNodeEx().asNode() = source and - (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and - flowsink.getNodeEx().asNode() = sink -} - -/** - * Holds if data can flow (inter-procedurally) from `source` to `sink`. - * - * Will only have results if `configuration` has non-empty sources and - * sinks. - */ -predicate flowsTo(Node source, Node sink, Configuration configuration) { - flowsTo(_, _, source, sink, configuration) -} - -private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { - fwd = true and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and - tuples = count(PathNode pn) - or - fwd = false and - nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and - fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and - conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and - 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(NodeEx node1, NodeEx node2 | - jumpStep(node1, node2, config) - or - additionalJumpStep(node1, node2, config) - or - // flow into callable - viableParamArgEx(_, node2, node1) - or - // flow out of a callable - viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) - | - c1 = node1.getEnclosingCallable() and - c2 = node2.getEnclosingCallable() and - c1 != c2 - ) - } - - private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSource(n) and c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSrc(mid, config) and callableStep(mid, c, config) - ) - } - - private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { - exists(Node n | config.isSink(n) and c = getNodeEnclosingCallable(n)) - or - exists(DataFlowCallable mid | - interestingCallableSink(mid, config) and callableStep(c, mid, config) - ) - } - - private newtype TCallableExt = - TCallable(DataFlowCallable c, Configuration config) { - interestingCallableSrc(c, config) or - interestingCallableSink(c, config) - } or - TCallableSrc() or - TCallableSink() - - private predicate callableExtSrc(TCallableSrc src) { any() } - - private predicate callableExtSink(TCallableSink sink) { any() } - - private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { - exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | - callableStep(c1, c2, config) and - ce1 = TCallable(c1, pragma[only_bind_into](config)) and - ce2 = TCallable(c2, pragma[only_bind_into](config)) - ) - or - exists(Node n, Configuration config | - ce1 = TCallableSrc() and - config.isSource(n) and - ce2 = TCallable(getNodeEnclosingCallable(n), config) - ) - or - exists(Node n, Configuration config | - ce2 = TCallableSink() and - config.isSink(n) and - ce1 = TCallable(getNodeEnclosingCallable(n), config) - ) - } - - private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { - callableExtStepFwd(ce2, ce1) - } - - private int distSrcExt(TCallableExt c) = - shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) - - private int distSinkExt(TCallableExt c) = - shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) - - private int distSrc(DataFlowCallable c, Configuration config) { - result = distSrcExt(TCallable(c, config)) - 1 - } - - private int distSink(DataFlowCallable c, Configuration config) { - result = distSinkExt(TCallable(c, config)) - 1 - } - - private newtype TPartialAccessPath = - TPartialNil(DataFlowType t) or - TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to - * a given node with a given `AccessPath`, this indicates the sequence of - * dereference operations needed to get from the value in the node to the - * tracked object. The final type indicates the type of the tracked object. - */ - private class PartialAccessPath extends TPartialAccessPath { - abstract string toString(); - - TypedContent getHead() { this = TPartialCons(result, _) } - - int len() { - this = TPartialNil(_) and result = 0 - or - this = TPartialCons(_, result) - } - - DataFlowType getType() { - this = TPartialNil(result) - or - exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) - } - } - - private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { - override string toString() { - exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) - } - } - - private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { - override string toString() { - exists(TypedContent tc, int len | this = TPartialCons(tc, len) | - if len = 1 - then result = "[" + tc.toString() + "]" - else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TRevPartialAccessPath = - TRevPartialNil() or - TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } - - /** - * Conceptually a list of `Content`s, but only the first - * element of the list and its length are tracked. - */ - private class RevPartialAccessPath extends TRevPartialAccessPath { - abstract string toString(); - - Content getHead() { this = TRevPartialCons(result, _) } - - int len() { - this = TRevPartialNil() and result = 0 - or - this = TRevPartialCons(_, result) - } - } - - private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { - override string toString() { result = "" } - } - - private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { - override string toString() { - exists(Content c, int len | this = TRevPartialCons(c, len) | - if len = 1 - then result = "[" + c.toString() + "]" - else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" - ) - } - } - - private newtype TSummaryCtx1 = - TSummaryCtx1None() or - TSummaryCtx1Param(ParamNodeEx p) - - private newtype TSummaryCtx2 = - TSummaryCtx2None() or - TSummaryCtx2Some(PartialAccessPath ap) - - private newtype TRevSummaryCtx1 = - TRevSummaryCtx1None() or - TRevSummaryCtx1Some(ReturnPosition pos) - - private newtype TRevSummaryCtx2 = - TRevSummaryCtx2None() or - TRevSummaryCtx2Some(RevPartialAccessPath ap) - - private newtype TPartialPathNode = - TPartialPathNodeFwd( - NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, - Configuration config - ) { - sourceNode(node, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - ap = TPartialNil(node.getDataFlowType()) and - not fullBarrier(node, config) and - exists(config.explorationLimit()) - or - partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and - distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() - } or - TPartialPathNodeRev( - NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap, - Configuration config - ) { - sinkNode(node, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - ap = TRevPartialNil() and - not fullBarrier(node, config) and - exists(config.explorationLimit()) - or - exists(PartialPathNodeRev mid | - revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContentCached(node.asNode(), ap.getHead()) and - not fullBarrier(node, config) and - distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() - ) - } - - pragma[nomagic] - private predicate partialPathNodeMk0( - NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, - Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStep(mid, node, cc, sc1, sc2, ap, config) and - not fullBarrier(node, config) and - not clearsContentCached(node.asNode(), ap.getHead().getContent()) and - if node.asNode() instanceof CastingNode - then compatibleTypes(node.getDataFlowType(), ap.getType()) - else any() - ) - } - - /** - * A `Node` augmented with a call context, an access path, and a configuration. - */ - class PartialPathNode extends TPartialPathNode { - /** Gets a textual representation of this element. */ - string toString() { result = this.getNodeEx().toString() + this.ppAp() } - - /** - * Gets a textual representation of this element, including a textual - * representation of the call context. - */ - string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() - } - - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** Gets the underlying `Node`. */ - final Node getNode() { this.getNodeEx().projectToNode() = result } - - private NodeEx getNodeEx() { - result = this.(PartialPathNodeFwd).getNodeEx() or - result = this.(PartialPathNodeRev).getNodeEx() - } - - /** Gets the associated configuration. */ - Configuration getConfiguration() { none() } - - /** Gets a successor of this node, if any. */ - PartialPathNode getASuccessor() { none() } - - /** - * Gets the approximate distance to the nearest source measured in number - * of interprocedural steps. - */ - int getSourceDistance() { - result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - /** - * Gets the approximate distance to the nearest sink measured in number - * of interprocedural steps. - */ - int getSinkDistance() { - result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) - } - - private string ppAp() { - exists(string s | - s = this.(PartialPathNodeFwd).getAp().toString() or - s = this.(PartialPathNodeRev).getAp().toString() - | - if s = "" then result = "" else result = " " + s - ) - } - - private string ppCtx() { - result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" - } - - /** Holds if this is a source in a forward-flow path. */ - predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } - - /** Holds if this is a sink in a reverse-flow path. */ - predicate isRevSink() { this.(PartialPathNodeRev).isSink() } - } - - /** - * Provides the query predicates needed to include a graph in a path-problem query. - */ - module PartialPathGraph { - /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ - query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } - } - - private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { - NodeEx node; - CallContext cc; - TSummaryCtx1 sc1; - TSummaryCtx2 sc2; - PartialAccessPath ap; - Configuration config; - - PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) } - - NodeEx getNodeEx() { result = node } - - CallContext getCallContext() { result = cc } - - TSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TSummaryCtx2 getSummaryCtx2() { result = sc2 } - - PartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeFwd getASuccessor() { - partialPathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx1(), - result.getSummaryCtx2(), result.getAp(), result.getConfiguration()) - } - - predicate isSource() { - sourceNode(node, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - ap instanceof TPartialNil - } - } - - private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { - NodeEx node; - TRevSummaryCtx1 sc1; - TRevSummaryCtx2 sc2; - RevPartialAccessPath ap; - Configuration config; - - PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) } - - NodeEx getNodeEx() { result = node } - - TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } - - TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } - - RevPartialAccessPath getAp() { result = ap } - - override Configuration getConfiguration() { result = config } - - override PartialPathNodeRev getASuccessor() { - revPartialPathStep(result, this.getNodeEx(), this.getSummaryCtx1(), this.getSummaryCtx2(), - this.getAp(), this.getConfiguration()) - } - - predicate isSink() { - sinkNode(node, config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - ap = TRevPartialNil() - } - } - - private predicate partialPathStep( - PartialPathNodeFwd mid, NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - PartialAccessPath ap, Configuration config - ) { - not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and - ( - localFlowStep(mid.getNodeEx(), node, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(mid.getNodeEx(), node, config) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - ) - or - jumpStep(mid.getNodeEx(), node, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(mid.getNodeEx(), node, config) and - cc instanceof CallContextAny and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() and - mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(node.getDataFlowType()) and - config = mid.getConfiguration() - or - partialPathStoreStep(mid, _, _, node, ap) and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - config = mid.getConfiguration() - or - exists(PartialAccessPath ap0, TypedContent tc | - partialPathReadStep(mid, ap0, tc, node, cc, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - apConsFwd(ap, tc, ap0, config) - ) - or - partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) - or - partialPathOutOfCallable(mid, node, cc, ap, config) and - sc1 = TSummaryCtx1None() and - sc2 = TSummaryCtx2None() - or - partialPathThroughCallable(mid, node, cc, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() - } - - bindingset[result, i] - private int unbindInt(int i) { i <= result and i >= result } - - pragma[inline] - private predicate partialPathStoreStep( - PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, - PartialAccessPath ap2 - ) { - exists(NodeEx midNode, DataFlowType contentType | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - store(midNode, tc, node, contentType, mid.getConfiguration()) and - ap2.getHead() = tc and - ap2.len() = unbindInt(ap1.len() + 1) and - compatibleTypes(ap1.getType(), contentType) - ) - } - - pragma[nomagic] - private predicate apConsFwd( - PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeFwd mid | - partialPathStoreStep(mid, ap1, tc, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathReadStep( - PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, - Configuration config - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and - ap.getHead() = tc and - pragma[only_bind_into](config) = mid.getConfiguration() and - cc = mid.getCallContext() - ) - } - - private predicate partialPathOutOfCallable0( - PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap, - Configuration config - ) { - pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and - innercc = mid.getCallContext() and - innercc instanceof CallContextNoCall and - ap = mid.getAp() and - config = mid.getConfiguration() - } - - pragma[nomagic] - private predicate partialPathOutOfCallable1( - PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | - partialPathOutOfCallable0(mid, pos, innercc, ap, config) and - c = pos.getCallable() and - kind = pos.getKind() and - resolveReturn(innercc, c, call) - | - if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() - ) - } - - private predicate partialPathOutOfCallable( - PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config - ) { - exists(ReturnKindExt kind, DataFlowCall call | - partialPathOutOfCallable1(mid, call, kind, cc, ap, config) - | - out.asNode() = kind.getAnOutNode(call) - ) - } - - pragma[noinline] - private predicate partialPathIntoArg( - PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, - Configuration config - ) { - exists(ArgNode arg | - arg = mid.getNodeEx().asNode() and - cc = mid.getCallContext() and - arg.argumentOf(call, i) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate partialPathIntoCallable0( - PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc, - DataFlowCall call, PartialAccessPath ap, Configuration config - ) { - partialPathIntoArg(mid, i, outercc, call, ap, config) and - callable = resolveCall(call, outercc) - } - - private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, - TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, - Configuration config - ) { - exists(int i, DataFlowCallable callable | - partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and - p.isParameterOf(callable, i) and - sc1 = TSummaryCtx1Param(p) and - sc2 = TSummaryCtx2Some(ap) - | - if recordDataFlowCallSite(call, callable) - then innercc = TSpecificCall(call) - else innercc = TSomeCall() - ) - } - - pragma[nomagic] - private predicate paramFlowsThroughInPartialPath( - ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, - PartialAccessPath ap, Configuration config - ) { - exists(PartialPathNodeFwd mid, RetNodeEx ret | - mid.getNodeEx() = ret and - kind = ret.getKind() and - cc = mid.getCallContext() and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - config = mid.getConfiguration() and - ap = mid.getAp() - ) - } - - pragma[noinline] - private predicate partialPathThroughCallable0( - DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, - PartialAccessPath ap, Configuration config - ) { - exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | - partialPathIntoCallable(mid, _, cc, innercc, sc1, sc2, call, _, config) and - paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) - ) - } - - private predicate partialPathThroughCallable( - PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config - ) { - exists(DataFlowCall call, ReturnKindExt kind | - partialPathThroughCallable0(call, mid, kind, cc, ap, config) and - out.asNode() = kind.getAnOutNode(call) - ) - } - - private predicate revPartialPathStep( - PartialPathNodeRev mid, NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, - RevPartialAccessPath ap, Configuration config - ) { - localFlowStep(node, mid.getNodeEx(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalLocalFlowStep(node, mid.getNodeEx(), config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - jumpStep(node, mid.getNodeEx(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - ap = mid.getAp() and - config = mid.getConfiguration() - or - additionalJumpStep(node, mid.getNodeEx(), config) and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - mid.getAp() instanceof RevPartialAccessPathNil and - ap = TRevPartialNil() and - config = mid.getConfiguration() - or - revPartialPathReadStep(mid, _, _, node, ap) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - config = mid.getConfiguration() - or - exists(RevPartialAccessPath ap0, Content c | - revPartialPathStoreStep(mid, ap0, c, node, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - apConsRev(ap, c, ap0, config) - ) - or - exists(ParamNodeEx p | - mid.getNodeEx() = p and - viableParamArgEx(_, p, node) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - sc1 = TRevSummaryCtx1None() and - sc2 = TRevSummaryCtx2None() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - or - exists(ReturnPosition pos | - revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and - pos = getReturnPosition(node.asNode()) - ) - or - revPartialPathThroughCallable(mid, node, ap, config) and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() - } - - pragma[inline] - private predicate revPartialPathReadStep( - PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, - RevPartialAccessPath ap2 - ) { - exists(NodeEx midNode | - midNode = mid.getNodeEx() and - ap1 = mid.getAp() and - read(node, c, midNode, mid.getConfiguration()) and - ap2.getHead() = c and - ap2.len() = unbindInt(ap1.len() + 1) - ) - } - - pragma[nomagic] - private predicate apConsRev( - RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config - ) { - exists(PartialPathNodeRev mid | - revPartialPathReadStep(mid, ap1, c, _, ap2) and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathStoreStep( - PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config - ) { - exists(NodeEx midNode, TypedContent tc | - midNode = mid.getNodeEx() and - ap = mid.getAp() and - store(node, tc, midNode, _, config) and - ap.getHead() = c and - config = mid.getConfiguration() and - tc.getContent() = c - ) - } - - pragma[nomagic] - private predicate revPartialPathIntoReturn( - PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, - DataFlowCall call, RevPartialAccessPath ap, Configuration config - ) { - exists(NodeEx out | - mid.getNodeEx() = out and - viableReturnPosOutEx(call, pos, out) and - sc1 = TRevSummaryCtx1Some(pos) and - sc2 = TRevSummaryCtx2Some(ap) and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathFlowsThrough( - int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, - Configuration config - ) { - exists(PartialPathNodeRev mid, ParamNodeEx p | - mid.getNodeEx() = p and - p.getPosition() = pos and - sc1 = mid.getSummaryCtx1() and - sc2 = mid.getSummaryCtx2() and - ap = mid.getAp() and - config = mid.getConfiguration() - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable0( - DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap, - Configuration config - ) { - exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 | - revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and - revPartialPathFlowsThrough(pos, sc1, sc2, ap, config) - ) - } - - pragma[nomagic] - private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgNodeEx node, RevPartialAccessPath ap, Configuration config - ) { - exists(DataFlowCall call, int pos | - revPartialPathThroughCallable0(call, mid, pos, ap, config) and - node.asNode().(ArgNode).argumentOf(call, pos) - ) - } -} - -import FlowExploration - -private predicate partialFlow( - PartialPathNode source, PartialPathNode node, Configuration configuration -) { - source.getConfiguration() = configuration and - source.isFwdSource() and - node = source.getASuccessor+() -} - -private predicate revPartialFlow( - PartialPathNode node, PartialPathNode sink, Configuration configuration -) { - sink.getConfiguration() = configuration and - sink.isRevSink() and - node.getASuccessor+() = sink -} diff --git a/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll b/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll deleted file mode 100644 index ea987acb2ea..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowImplCommon.qll +++ /dev/null @@ -1,1294 +0,0 @@ -private import DataFlowImplSpecific::Private -private import DataFlowImplSpecific::Public -import Cached - -/** - * The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion. - * - * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the - * estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of - * these limits are represented with lower precision during pruning. - */ -predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) { - apLimit = 10 and - tupleLimit = 10000 -} - -/** - * The cost limits for the `AccessPathApprox` to `AccessPath` expansion. - * - * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the - * estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of - * these limits are represented with lower precision. - */ -predicate accessPathCostLimits(int apLimit, int tupleLimit) { - apLimit = 5 and - tupleLimit = 1000 -} - -/** - * Provides a simple data-flow analysis for resolving lambda calls. The analysis - * currently excludes read-steps, store-steps, and flow-through. - * - * The analysis uses non-linear recursion: When computing a flow path in or out - * of a call, we use the results of the analysis recursively to resolve lambda - * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. - */ -private module LambdaFlow { - private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { - p.isParameterOf(viableCallable(call), i) - } - - private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { - p.isParameterOf(viableCallableLambda(call, _), i) - } - - private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { - exists(int i | - viableParamNonLambda(call, i, p) and - arg.argumentOf(call, i) - ) - } - - private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { - exists(int i | - viableParamLambda(call, i, p) and - arg.argumentOf(call, i) - ) - } - - private newtype TReturnPositionSimple = - TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) { - exists(ReturnNode ret | - c = getNodeEnclosingCallable(ret) and - kind = ret.getKind() - ) - } - - pragma[noinline] - private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) { - result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind) - } - - pragma[nomagic] - private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) { - result = TReturnPositionSimple0(viableCallable(call), kind) - } - - pragma[nomagic] - private TReturnPositionSimple viableReturnPosLambda( - DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind - ) { - result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind) - } - - private predicate viableReturnPosOutNonLambda( - DataFlowCall call, TReturnPositionSimple pos, OutNode out - ) { - exists(ReturnKind kind | - pos = viableReturnPosNonLambda(call, kind) and - out = getAnOutNode(call, kind) - ) - } - - private predicate viableReturnPosOutLambda( - DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out - ) { - exists(ReturnKind kind | - pos = viableReturnPosLambda(call, lastCall, kind) and - out = getAnOutNode(call, kind) - ) - } - - /** - * Holds if data can flow (inter-procedurally) from `node` (of type `t`) to - * the lambda call `lambdaCall`. - * - * The parameter `toReturn` indicates whether the path from `node` to - * `lambdaCall` goes through a return, and `toJump` whether the path goes - * through a jump step. - * - * The call context `lastCall` records the last call on the path from `node` - * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing - * callable of `lambdaCall`. - */ - pragma[nomagic] - predicate revLambdaFlow( - DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, - boolean toJump, DataFlowCallOption lastCall - ) { - revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and - if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode - then compatibleTypes(t, getNodeDataFlowType(node)) - else any() - } - - pragma[nomagic] - predicate revLambdaFlow0( - DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, - boolean toJump, DataFlowCallOption lastCall - ) { - lambdaCall(lambdaCall, kind, node) and - t = getNodeDataFlowType(node) and - toReturn = false and - toJump = false and - lastCall = TDataFlowCallNone() - or - // local flow - exists(Node mid, DataFlowType t0 | - revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall) - | - simpleLocalFlowStep(node, mid) and - t = t0 - or - exists(boolean preservesValue | - additionalLambdaFlowStep(node, mid, preservesValue) and - getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) - | - preservesValue = false and - t = getNodeDataFlowType(node) - or - preservesValue = true and - t = t0 - ) - ) - or - // jump step - exists(Node mid, DataFlowType t0 | - revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and - toReturn = false and - toJump = true and - lastCall = TDataFlowCallNone() - | - jumpStepCached(node, mid) and - t = t0 - or - exists(boolean preservesValue | - additionalLambdaFlowStep(node, mid, preservesValue) and - getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) - | - preservesValue = false and - t = getNodeDataFlowType(node) - or - preservesValue = true and - t = t0 - ) - ) - or - // flow into a callable - exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | - revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and - ( - if lastCall0 = TDataFlowCallNone() and toJump = false - then lastCall = TDataFlowCallSome(call) - else lastCall = lastCall0 - ) and - toReturn = false - | - viableParamArgNonLambda(call, p, node) - or - viableParamArgLambda(call, p, node) // non-linear recursion - ) - or - // flow out of a callable - exists(TReturnPositionSimple pos | - revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and - getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and - toReturn = true - ) - } - - pragma[nomagic] - predicate revLambdaFlowOutLambdaCall( - DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump, - DataFlowCall call, DataFlowCallOption lastCall - ) { - revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and - exists(ReturnKindExt rk | - out = rk.getAnOutNode(call) and - lambdaCall(call, _, _) - ) - } - - pragma[nomagic] - predicate revLambdaFlowOut( - DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t, - boolean toJump, DataFlowCallOption lastCall - ) { - exists(DataFlowCall call, OutNode out | - revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and - viableReturnPosOutNonLambda(call, pos, out) - or - // non-linear recursion - revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and - viableReturnPosOutLambda(call, _, pos, out) - ) - } - - pragma[nomagic] - predicate revLambdaFlowIn( - DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, - DataFlowCallOption lastCall - ) { - revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) - } -} - -private DataFlowCallable viableCallableExt(DataFlowCall call) { - result = viableCallable(call) - or - result = viableCallableLambda(call, _) -} - -cached -private module Cached { - /** - * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to - * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby - * collapsing the two stages. - */ - cached - predicate forceCachingInSameStage() { any() } - - cached - predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } - - cached - predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { - c = call.getEnclosingCallable() - } - - cached - predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } - - cached - predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } - - cached - predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } - - cached - predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } - - cached - predicate outNodeExt(Node n) { - n instanceof OutNode - or - n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode - } - - cached - predicate hiddenNode(Node n) { nodeIsHidden(n) } - - cached - OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { - result = getAnOutNode(call, k.(ValueReturnKind).getKind()) - or - exists(ArgNode arg | - result.(PostUpdateNode).getPreUpdateNode() = arg and - arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) - ) - } - - cached - predicate returnNodeExt(Node n, ReturnKindExt k) { - k = TValueReturn(n.(ReturnNode).getKind()) - or - exists(ParamNode p, int pos | - parameterValueFlowsToPreUpdate(p, n) and - p.isParameterOf(_, pos) and - k = TParamUpdate(pos) - ) - } - - cached - predicate castNode(Node n) { n instanceof CastNode } - - cached - predicate castingNode(Node n) { - castNode(n) or - n instanceof ParamNode or - n instanceof OutNodeExt or - // For reads, `x.f`, we want to check that the tracked type after the read (which - // is obtained by popping the head of the access path stack) is compatible with - // the type of `x.f`. - read(_, _, n) - } - - cached - predicate parameterNode(Node n, DataFlowCallable c, int i) { - n.(ParameterNode).isParameterOf(c, i) - } - - cached - predicate argumentNode(Node n, DataFlowCall call, int pos) { - n.(ArgumentNode).argumentOf(call, pos) - } - - /** - * Gets a viable target for the lambda call `call`. - * - * `lastCall` records the call required to reach `call` in order for the result - * to be a viable target, if any. - */ - cached - DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) { - exists(Node creation, LambdaCallKind kind | - LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and - lambdaCreation(creation, kind, result) - ) - } - - /** - * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. - * The instance parameter is considered to have index `-1`. - */ - pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParamNode p) { - p.isParameterOf(viableCallableExt(call), i) - } - - /** - * Holds if `arg` is a possible argument to `p` in `call`, taking virtual - * dispatch into account. - */ - cached - predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { - exists(int i | - viableParam(call, i, p) and - arg.argumentOf(call, i) and - compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) - ) - } - - pragma[nomagic] - private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { - viableCallableExt(call) = result.getCallable() and - kind = result.getKind() - } - - /** - * Holds if a value at return position `pos` can be returned to `out` via `call`, - * taking virtual dispatch into account. - */ - cached - predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) { - exists(ReturnKindExt kind | - pos = viableReturnPos(call, kind) and - out = kind.getAnOutNode(call) - ) - } - - /** Provides predicates for calculating flow-through summaries. */ - private module FlowThrough { - /** - * The first flow-through approximation: - * - * - Input access paths are abstracted with a Boolean parameter - * that indicates (non-)emptiness. - */ - private module Cand { - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps. - * - * `read` indicates whether it is contents of `p` that can flow to `node`. - */ - pragma[nomagic] - private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { - p = node and - read = false - or - // local flow - exists(Node mid | - parameterValueFlowCand(p, mid, read) and - simpleLocalFlowStep(mid, node) - ) - or - // read - exists(Node mid | - parameterValueFlowCand(p, mid, false) and - read(mid, _, node) and - read = true - ) - or - // flow through: no prior read - exists(ArgNode arg | - parameterValueFlowArgCand(p, arg, false) and - argumentValueFlowsThroughCand(arg, node, read) - ) - or - // flow through: no read inside method - exists(ArgNode arg | - parameterValueFlowArgCand(p, arg, read) and - argumentValueFlowsThroughCand(arg, node, false) - ) - } - - pragma[nomagic] - private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { - parameterValueFlowCand(p, arg, read) - } - - pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { - parameterValueFlowCand(p, n.getPreUpdateNode(), false) - } - - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps, not taking call contexts - * into account. - * - * `read` indicates whether it is contents of `p` that can flow to the return - * node. - */ - predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { - exists(ReturnNode ret | - parameterValueFlowCand(p, ret, read) and - kind = ret.getKind() - ) - } - - pragma[nomagic] - private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read - ) { - exists(ParamNode param | viableParamArg(call, param, arg) | - parameterValueFlowReturnCand(param, kind, read) - ) - } - - /** - * Holds if `arg` flows to `out` through a call using only value-preserving steps, - * not taking call contexts into account. - * - * `read` indicates whether it is contents of `arg` that can flow to `out`. - */ - predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { - exists(DataFlowCall call, ReturnKind kind | - argumentValueFlowsThroughCand0(call, arg, kind, read) and - out = getAnOutNode(call, kind) - ) - } - - predicate cand(ParamNode p, Node n) { - parameterValueFlowCand(p, n, _) and - ( - parameterValueFlowReturnCand(p, _, _) - or - parameterValueFlowsToPreUpdateCand(p, _) - ) - } - } - - /** - * The final flow-through calculation: - * - * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`) - * or summarized as a single read step with before and after types recorded - * in the `ReadStepTypesOption` parameter. - * - Types are checked using the `compatibleTypes()` relation. - */ - private module Final { - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps and possibly a single read step, not taking - * call contexts into account. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { - parameterValueFlow0(p, node, read) and - if node instanceof CastingNode - then - // normal flow through - read = TReadStepTypesNone() and - compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) - or - // getter - compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) - else any() - } - - pragma[nomagic] - private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { - p = node and - Cand::cand(p, _) and - read = TReadStepTypesNone() - or - // local flow - exists(Node mid | - parameterValueFlow(p, mid, read) and - simpleLocalFlowStep(mid, node) - ) - or - // read - exists(Node mid | - parameterValueFlow(p, mid, TReadStepTypesNone()) and - readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, - read.getContentType()) and - Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) - ) - or - parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) - } - - pragma[nomagic] - private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read - ) { - // flow through: no prior read - exists(ArgNode arg | - parameterValueFlowArg(p, arg, mustBeNone) and - argumentValueFlowsThrough(arg, read, node) - ) - or - // flow through: no read inside method - exists(ArgNode arg | - parameterValueFlowArg(p, arg, read) and - argumentValueFlowsThrough(arg, mustBeNone, node) - ) - } - - pragma[nomagic] - private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { - parameterValueFlow(p, arg, read) and - Cand::argumentValueFlowsThroughCand(arg, _, _) - } - - pragma[nomagic] - private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read - ) { - exists(ParamNode param | viableParamArg(call, param, arg) | - parameterValueFlowReturn(param, kind, read) - ) - } - - /** - * Holds if `arg` flows to `out` through a call using only - * value-preserving steps and possibly a single read step, not taking - * call contexts into account. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - pragma[nomagic] - predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { - exists(DataFlowCall call, ReturnKind kind | - argumentValueFlowsThrough0(call, arg, kind, read) and - out = getAnOutNode(call, kind) - | - // normal flow through - read = TReadStepTypesNone() and - compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) - or - // getter - compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and - compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) - ) - } - - /** - * Holds if `arg` flows to `out` through a call using only - * value-preserving steps and a single read step, not taking call - * contexts into account, thus representing a getter-step. - */ - predicate getterStep(ArgNode arg, Content c, Node out) { - argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) - } - - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps and possibly a single read - * step. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - private predicate parameterValueFlowReturn( - ParamNode p, ReturnKind kind, ReadStepTypesOption read - ) { - exists(ReturnNode ret | - parameterValueFlow(p, ret, read) and - kind = ret.getKind() - ) - } - } - - import Final - } - - import FlowThrough - - cached - private module DispatchWithCallContext { - /** - * Holds if the set of viable implementations that can be called by `call` - * might be improved by knowing the call context. - */ - pragma[nomagic] - private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { - mayBenefitFromCallContext(call, callable) - or - callEnclosingCallable(call, callable) and - exists(viableCallableLambda(call, TDataFlowCallSome(_))) - } - - /** - * Gets a viable dispatch target of `call` in the context `ctx`. This is - * restricted to those `call`s for which a context might make a difference. - */ - pragma[nomagic] - private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) { - result = viableImplInCallContext(call, ctx) - or - result = viableCallableLambda(call, TDataFlowCallSome(ctx)) - or - exists(DataFlowCallable enclosing | - mayBenefitFromCallContextExt(call, enclosing) and - enclosing = viableCallableExt(ctx) and - result = viableCallableLambda(call, TDataFlowCallNone()) - ) - } - - /** - * Holds if the call context `ctx` reduces the set of viable run-time - * dispatch targets of call `call` in `c`. - */ - cached - predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) { - exists(int tgts, int ctxtgts | - mayBenefitFromCallContextExt(call, c) and - c = viableCallableExt(ctx) and - ctxtgts = count(viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(viableCallableExt(call)) and - ctxtgts < tgts - ) - } - - /** - * Gets a viable run-time dispatch target for the call `call` in the - * context `ctx`. This is restricted to those calls for which a context - * makes a difference. - */ - cached - DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { - result = viableImplInCallContextExt(call, ctx) and - reducedViableImplInCallContext(call, _, ctx) - } - - /** - * Holds if flow returning from callable `c` to call `call` might return - * further and if this path restricts the set of call sites that can be - * returned to. - */ - cached - predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) { - exists(int tgts, int ctxtgts | - mayBenefitFromCallContextExt(call, _) and - c = viableCallableExt(call) and - ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and - ctxtgts < tgts - ) - } - - /** - * Gets a viable run-time dispatch target for the call `call` in the - * context `ctx`. This is restricted to those calls and results for which - * the return flow from the result to `call` restricts the possible context - * `ctx`. - */ - cached - DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) { - result = viableImplInCallContextExt(call, ctx) and - reducedViableImplInReturn(result, call) - } - } - - import DispatchWithCallContext - - /** - * Holds if `p` can flow to the pre-update node associated with post-update - * node `n`, in the same callable, using only value-preserving steps. - */ - private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { - parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) - } - - private predicate store( - Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType - ) { - storeStep(node1, c, node2) and - contentType = getNodeDataFlowType(node1) and - containerType = getNodeDataFlowType(node2) - or - exists(Node n1, Node n2 | - n1 = node1.(PostUpdateNode).getPreUpdateNode() and - n2 = node2.(PostUpdateNode).getPreUpdateNode() - | - argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) - or - read(n2, c, n1) and - contentType = getNodeDataFlowType(n1) and - containerType = getNodeDataFlowType(n2) - ) - } - - cached - predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } - - /** - * Holds if data can flow from `node1` to `node2` via a direct assignment to - * `f`. - * - * This includes reverse steps through reads when the result of the read has - * been stored into, in order to handle cases like `x.f1.f2 = y`. - */ - cached - predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) { - store(node1, tc.getContent(), node2, contentType, tc.getContainerType()) - } - - /** - * Holds if data can flow from `fromNode` to `toNode` because they are the post-update - * nodes of some function output and input respectively, where the output and input - * are aliases. A typical example is a function returning `this`, implementing a fluent - * interface. - */ - private predicate reverseStepThroughInputOutputAlias( - PostUpdateNode fromNode, PostUpdateNode toNode - ) { - exists(Node fromPre, Node toPre | - fromPre = fromNode.getPreUpdateNode() and - toPre = toNode.getPreUpdateNode() - | - exists(DataFlowCall c | - // Does the language-specific simpleLocalFlowStep already model flow - // from function input to output? - fromPre = getAnOutNode(c, _) and - toPre.(ArgNode).argumentOf(c, _) and - simpleLocalFlowStep(toPre.(ArgNode), fromPre) - ) - or - argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) - ) - } - - cached - predicate simpleLocalFlowStepExt(Node node1, Node node2) { - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - } - - /** - * Holds if the call context `call` improves virtual dispatch in `callable`. - */ - cached - predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) { - reducedViableImplInCallContext(_, callable, call) - } - - /** - * Holds if the call context `call` allows us to prune unreachable nodes in `callable`. - */ - cached - predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) - } - - cached - newtype TCallContext = - TAnyCallContext() or - TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or - TSomeCall() or - TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } - - cached - newtype TReturnPosition = - TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) { - exists(ReturnNodeExt ret | - c = returnNodeGetEnclosingCallable(ret) and - kind = ret.getKind() - ) - } - - cached - newtype TLocalFlowCallContext = - TAnyLocalCall() or - TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } - - cached - newtype TReturnKindExt = - TValueReturn(ReturnKind kind) or - TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } - - cached - newtype TBooleanOption = - TBooleanNone() or - TBooleanSome(boolean b) { b = true or b = false } - - cached - newtype TDataFlowCallOption = - TDataFlowCallNone() or - TDataFlowCallSome(DataFlowCall call) - - cached - newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) } - - cached - newtype TAccessPathFront = - TFrontNil(DataFlowType t) or - TFrontHead(TypedContent tc) - - cached - newtype TAccessPathFrontOption = - TAccessPathFrontNone() or - TAccessPathFrontSome(AccessPathFront apf) -} - -/** - * Holds if the call context `call` either improves virtual dispatch in - * `callable` or if it allows us to prune unreachable nodes in `callable`. - */ -predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { - recordDataFlowCallSiteDispatch(call, callable) or - recordDataFlowCallSiteUnreachable(call, callable) -} - -/** - * A `Node` at which a cast can occur such that the type should be checked. - */ -class CastingNode extends Node { - CastingNode() { castingNode(this) } -} - -private predicate readStepWithTypes( - Node n1, DataFlowType container, Content c, Node n2, DataFlowType content -) { - read(n1, c, n2) and - container = getNodeDataFlowType(n1) and - content = getNodeDataFlowType(n2) -} - -private newtype TReadStepTypesOption = - TReadStepTypesNone() or - TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) { - readStepWithTypes(_, container, c, _, content) - } - -private class ReadStepTypesOption extends TReadStepTypesOption { - predicate isSome() { this instanceof TReadStepTypesSome } - - DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) } - - Content getContent() { this = TReadStepTypesSome(_, result, _) } - - DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) } - - string toString() { if this.isSome() then result = "Some(..)" else result = "None()" } -} - -/** - * A call context to restrict the targets of virtual dispatch, prune local flow, - * and match the call sites of flow into a method with flow out of a method. - * - * There are four cases: - * - `TAnyCallContext()` : No restrictions on method flow. - * - `TSpecificCall(DataFlowCall call)` : Flow entered through the - * given `call`. This call improves the set of viable - * dispatch targets for at least one method call in the current callable - * or helps prune unreachable nodes in the current callable. - * - `TSomeCall()` : Flow entered through a parameter. The - * originating call does not improve the set of dispatch targets for any - * method call in the current callable and was therefore not recorded. - * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and - * this dispatch target of `call` implies a reduced set of dispatch origins - * to which data may flow if it should reach a `return` statement. - */ -abstract class CallContext extends TCallContext { - abstract string toString(); - - /** Holds if this call context is relevant for `callable`. */ - abstract predicate relevantFor(DataFlowCallable callable); -} - -abstract class CallContextNoCall extends CallContext { } - -class CallContextAny extends CallContextNoCall, TAnyCallContext { - override string toString() { result = "CcAny" } - - override predicate relevantFor(DataFlowCallable callable) { any() } -} - -abstract class CallContextCall extends CallContext { - /** Holds if this call context may be `call`. */ - bindingset[call] - abstract predicate matchesCall(DataFlowCall call); -} - -class CallContextSpecificCall extends CallContextCall, TSpecificCall { - override string toString() { - exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")") - } - - override predicate relevantFor(DataFlowCallable callable) { - recordDataFlowCallSite(this.getCall(), callable) - } - - override predicate matchesCall(DataFlowCall call) { call = this.getCall() } - - DataFlowCall getCall() { this = TSpecificCall(result) } -} - -class CallContextSomeCall extends CallContextCall, TSomeCall { - override string toString() { result = "CcSomeCall" } - - override predicate relevantFor(DataFlowCallable callable) { - exists(ParamNode p | getNodeEnclosingCallable(p) = callable) - } - - override predicate matchesCall(DataFlowCall call) { any() } -} - -class CallContextReturn extends CallContextNoCall, TReturn { - override string toString() { - exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") - } - - override predicate relevantFor(DataFlowCallable callable) { - exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) - } -} - -/** - * A call context that is relevant for pruning local flow. - */ -abstract class LocalCallContext extends TLocalFlowCallContext { - abstract string toString(); - - /** Holds if this call context is relevant for `callable`. */ - abstract predicate relevantFor(DataFlowCallable callable); -} - -class LocalCallContextAny extends LocalCallContext, TAnyLocalCall { - override string toString() { result = "LocalCcAny" } - - override predicate relevantFor(DataFlowCallable callable) { any() } -} - -class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall { - LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) } - - DataFlowCall call; - - DataFlowCall getCall() { result = call } - - override string toString() { result = "LocalCcCall(" + call + ")" } - - override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) } -} - -private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) -} - -/** - * Gets the local call context given the call context and the callable that - * the contexts apply to. - */ -LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) { - ctx.relevantFor(callable) and - if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable) - then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall() - else result instanceof LocalCallContextAny -} - -/** - * The value of a parameter at function entry, viewed as a node in a data - * flow graph. - */ -class ParamNode extends Node { - ParamNode() { parameterNode(this, _, _) } - - /** - * Holds if this node is the parameter of callable `c` at the specified - * (zero-based) position. - */ - predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } -} - -/** A data-flow node that represents a call argument. */ -class ArgNode extends Node { - ArgNode() { argumentNode(this, _, _) } - - /** Holds if this argument occurs at the given position in the given call. */ - final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } -} - -/** - * A node from which flow can return to the caller. This is either a regular - * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. - */ -class ReturnNodeExt extends Node { - ReturnNodeExt() { returnNodeExt(this, _) } - - /** Gets the kind of this returned value. */ - ReturnKindExt getKind() { returnNodeExt(this, result) } -} - -/** - * A node to which data can flow from a call. Either an ordinary out node - * or a post-update node associated with a call argument. - */ -class OutNodeExt extends Node { - OutNodeExt() { outNodeExt(this) } -} - -/** - * An extended return kind. A return kind describes how data can be returned - * from a callable. This can either be through a returned value or an updated - * parameter. - */ -abstract class ReturnKindExt extends TReturnKindExt { - /** Gets a textual representation of this return kind. */ - abstract string toString(); - - /** Gets a node corresponding to data flow out of `call`. */ - final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } -} - -class ValueReturnKind extends ReturnKindExt, TValueReturn { - private ReturnKind kind; - - ValueReturnKind() { this = TValueReturn(kind) } - - ReturnKind getKind() { result = kind } - - override string toString() { result = kind.toString() } -} - -class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { - private int pos; - - ParamUpdateReturnKind() { this = TParamUpdate(pos) } - - int getPosition() { result = pos } - - override string toString() { result = "param update " + pos } -} - -/** A callable tagged with a relevant return kind. */ -class ReturnPosition extends TReturnPosition0 { - private DataFlowCallable c; - private ReturnKindExt kind; - - ReturnPosition() { this = TReturnPosition0(c, kind) } - - /** Gets the callable. */ - DataFlowCallable getCallable() { result = c } - - /** Gets the return kind. */ - ReturnKindExt getKind() { result = kind } - - /** Gets a textual representation of this return position. */ - string toString() { result = "[" + kind + "] " + c } -} - -/** - * Gets the enclosing callable of `n`. Unlike `n.getEnclosingCallable()`, this - * predicate ensures that joins go from `n` to the result instead of the other - * way around. - */ -pragma[inline] -DataFlowCallable getNodeEnclosingCallable(Node n) { - nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) -} - -/** Gets the type of `n` used for type pruning. */ -pragma[inline] -DataFlowType getNodeDataFlowType(Node n) { - nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) -} - -pragma[noinline] -private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) { - result = getNodeEnclosingCallable(ret) -} - -pragma[noinline] -private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) { - result.getCallable() = returnNodeGetEnclosingCallable(ret) and - kind = result.getKind() -} - -pragma[noinline] -ReturnPosition getReturnPosition(ReturnNodeExt ret) { - result = getReturnPosition0(ret, ret.getKind()) -} - -/** - * Checks whether `inner` can return to `call` in the call context `innercc`. - * Assumes a context of `inner = viableCallableExt(call)`. - */ -bindingset[innercc, inner, call] -predicate checkCallContextReturn(CallContext innercc, DataFlowCallable inner, DataFlowCall call) { - innercc instanceof CallContextAny - or - exists(DataFlowCallable c0, DataFlowCall call0 | - callEnclosingCallable(call0, inner) and - innercc = TReturn(c0, call0) and - c0 = prunedViableImplInCallContextReverse(call0, call) - ) -} - -/** - * Checks whether `call` can resolve to `calltarget` in the call context `cc`. - * Assumes a context of `calltarget = viableCallableExt(call)`. - */ -bindingset[cc, call, calltarget] -predicate checkCallContextCall(CallContext cc, DataFlowCall call, DataFlowCallable calltarget) { - exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | - if reducedViableImplInCallContext(call, _, ctx) - then calltarget = prunedViableImplInCallContext(call, ctx) - else any() - ) - or - cc instanceof CallContextSomeCall - or - cc instanceof CallContextAny - or - cc instanceof CallContextReturn -} - -/** - * Resolves a return from `callable` in `cc` to `call`. This is equivalent to - * `callable = viableCallableExt(call) and checkCallContextReturn(cc, callable, call)`. - */ -bindingset[cc, callable] -predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { - cc instanceof CallContextAny and callable = viableCallableExt(call) - or - exists(DataFlowCallable c0, DataFlowCall call0 | - callEnclosingCallable(call0, callable) and - cc = TReturn(c0, call0) and - c0 = prunedViableImplInCallContextReverse(call0, call) - ) -} - -/** - * Resolves a call from `call` in `cc` to `result`. This is equivalent to - * `result = viableCallableExt(call) and checkCallContextCall(cc, call, result)`. - */ -bindingset[call, cc] -DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { - exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | - if reducedViableImplInCallContext(call, _, ctx) - then result = prunedViableImplInCallContext(call, ctx) - else result = viableCallableExt(call) - ) - or - result = viableCallableExt(call) and cc instanceof CallContextSomeCall - or - result = viableCallableExt(call) and cc instanceof CallContextAny - or - result = viableCallableExt(call) and cc instanceof CallContextReturn -} - -/** An optional Boolean value. */ -class BooleanOption extends TBooleanOption { - string toString() { - this = TBooleanNone() and result = "" - or - this = TBooleanSome(any(boolean b | result = b.toString())) - } -} - -/** An optional `DataFlowCall`. */ -class DataFlowCallOption extends TDataFlowCallOption { - string toString() { - this = TDataFlowCallNone() and - result = "(none)" - or - exists(DataFlowCall call | - this = TDataFlowCallSome(call) and - result = call.toString() - ) - } -} - -/** Content tagged with the type of a containing object. */ -class TypedContent extends MkTypedContent { - private Content c; - private DataFlowType t; - - TypedContent() { this = MkTypedContent(c, t) } - - /** Gets the content. */ - Content getContent() { result = c } - - /** Gets the container type. */ - DataFlowType getContainerType() { result = t } - - /** Gets a textual representation of this content. */ - string toString() { result = c.toString() } - - /** - * Holds if access paths with this `TypedContent` at their head always should - * be tracked at high precision. This disables adaptive access path precision - * for such access paths. - */ - predicate forceHighPrecision() { forceHighPrecision(c) } -} - -/** - * The front of an access path. This is either a head or a nil. - */ -abstract class AccessPathFront extends TAccessPathFront { - abstract string toString(); - - abstract DataFlowType getType(); - - abstract boolean toBoolNonEmpty(); - - TypedContent getHead() { this = TFrontHead(result) } - - predicate isClearedAt(Node n) { clearsContentCached(n, this.getHead().getContent()) } -} - -class AccessPathFrontNil extends AccessPathFront, TFrontNil { - private DataFlowType t; - - AccessPathFrontNil() { this = TFrontNil(t) } - - override string toString() { result = ppReprType(t) } - - override DataFlowType getType() { result = t } - - override boolean toBoolNonEmpty() { result = false } -} - -class AccessPathFrontHead extends AccessPathFront, TFrontHead { - private TypedContent tc; - - AccessPathFrontHead() { this = TFrontHead(tc) } - - override string toString() { result = tc.toString() } - - override DataFlowType getType() { result = tc.getContainerType() } - - override boolean toBoolNonEmpty() { result = true } -} - -/** An optional access path front. */ -class AccessPathFrontOption extends TAccessPathFrontOption { - string toString() { - this = TAccessPathFrontNone() and result = "" - or - this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString())) - } -} \ No newline at end of file diff --git a/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll b/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll deleted file mode 100644 index 1e9cfd62da8..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowImplSpecific.qll +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Provides QL-specific definitions for use in the data flow library. - */ -module Private { - import DataFlowPrivate - import DataFlowDispatch -} - -module Public { - import DataFlowUtil -} \ No newline at end of file diff --git a/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll b/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll deleted file mode 100644 index eea5bdadc95..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowPrivate.qll +++ /dev/null @@ -1,171 +0,0 @@ -private import ql -private import codeql_ql.ast.internal.Builtins -private import DataFlowUtil - -/** - * A data flow node that occurs as the argument of a call and is passed as-is - * to the callable. Arguments that are wrapped in an implicit varargs array - * creation are not included, but the implicitly created array is. - * Instance arguments are also included. - */ -class ArgumentNode extends Node { - ArgumentNode() { exists(Argument arg | this.asExpr() = arg) } - - /** - * Holds if this argument occurs at the given position in the given call. - * The instance argument is considered to have index `-1`. - */ - predicate argumentOf(DataFlowCall call, int pos) { - exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition()) - } - - /** Gets the call in which this node is an argument. */ - DataFlowCall getCall() { this.argumentOf(result, _) } -} - -/** A `ReturnNode` that occurs as the result of a `ReturnStmt`. */ -private class NormalReturnNode extends ReturnNode, ExprNode { - NormalReturnNode() { this.getExpr() instanceof ResultAccess } - - /** Gets the kind of this returned value. */ - override ReturnKind getKind() { result = TNormalReturnKind() } -} - -private class ExprOutNode extends OutNode, ExprNode { - Call call; - - ExprOutNode() { call = this.getExpr() } - - /** Gets the underlying call. */ - override DataFlowCall getCall() { result = call } -} - -/** - * Gets a node that can read the value returned from `call` with return kind - * `kind`. - */ -OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { - result = call.getNode() and - kind = TNormalReturnKind() - or - result.(ArgumentOutNode).getCall() = call and - kind = TParameterOutKind(result.(ArgumentOutNode).getIndex()) -} - -/** - * Holds if data can flow from `node1` to `node2` in a way that loses the - * calling context. For example, this would happen with flow through a - * global or static variable. - */ -predicate jumpStep(Node n1, Node n2) { none() } - -/** - * Holds if data can flow from `node1` to `node2` via an assignment to `f`. - * Thus, `node2` references an object with a field `f` that contains the - * value of `node1`. - */ -predicate storeStep(Node node1, Content f, PostUpdateNode node2) { none() } - -/** - * Holds if data can flow from `node1` to `node2` via a read of `f`. - * Thus, `node1` references an object with a field `f` whose value ends up in - * `node2`. - */ -predicate readStep(Node node1, Content f, Node node2) { none() } - -/** - * Holds if values stored inside content `c` are cleared at node `n`. - */ -predicate clearsContent(Node n, Content c) { - none() // stub implementation -} - -/** Gets the type of `n` used for type pruning. */ -Type getNodeType(Node n) { - suppressUnusedNode(n) and - result instanceof IntClass // stub implementation -} - -/** Gets a string representation of a type returned by `getNodeType`. */ -string ppReprType(Type t) { none() } // stub implementation - -/** - * Holds if `t1` and `t2` are compatible, that is, whether data can flow from - * a node of type `t1` to a node of type `t2`. - */ -pragma[inline] -predicate compatibleTypes(Type t1, Type t2) { - any() // stub implementation -} - -private predicate suppressUnusedNode(Node n) { any() } - -////////////////////////////////////////////////////////////////////////////// -// Java QL library compatibility wrappers -////////////////////////////////////////////////////////////////////////////// -/** A node that performs a type cast. */ -class CastNode extends Node { - CastNode() { none() } // stub implementation -} - -class DataFlowExpr = Expr; - -class DataFlowType = Type; - -/** A function call relevant for data flow. */ -class DataFlowCall extends Expr instanceof Call { - /** - * Gets the nth argument for this call. - * - * The range of `n` is from `0` to `getNumberOfArguments() - 1`. - */ - Expr getArgument(int n) { result = super.getArgument(n) } - - /** Gets the data flow node corresponding to this call. */ - ExprNode getNode() { result.getExpr() = this } - - /** Gets the enclosing callable of this call. */ - DataFlowCallable getEnclosingCallable() { result.asPredicate() = this.getEnclosingPredicate() } -} - -predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation - -int accessPathLimit() { result = 5 } - -/** - * Holds if access paths with `c` at their head always should be tracked at high - * precision. This disables adaptive access path precision for such access paths. - */ -predicate forceHighPrecision(Content c) { none() } - -/** 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 - * freshly created object that is not saved in a variable. - * - * This predicate is only used for consistency checks. - */ -predicate isImmutableOrUnobservable(Node n) { none() } - -/** Holds if `n` should be hidden from path explanations. */ -predicate nodeIsHidden(Node n) { none() } - -class LambdaCallKind = Unit; - -/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ -predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() } - -/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */ -predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() } - -/** Extra data-flow steps needed for lambda flow analysis. */ -predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } diff --git a/ql/src/codeql/dataflow/internal/DataFlowUtil.qll b/ql/src/codeql/dataflow/internal/DataFlowUtil.qll deleted file mode 100644 index 7410f5b84cd..00000000000 --- a/ql/src/codeql/dataflow/internal/DataFlowUtil.qll +++ /dev/null @@ -1,513 +0,0 @@ -/** - * Provides QL-specific definitions for use in the data flow library. - */ - -private import ql - -cached -private newtype TNode = - TExprNode(Expr e) or - TOutParameterNode(Parameter p) or - TArgumentOutNode(VarAccess acc, Call call, int pos) { - acc.(Argument).getCall() = call and acc.(Argument).getPosition() = pos - } or - TParameterNode(Parameter p) - -/** An argument to a call. */ -class Argument extends Expr { - Call call; - int pos; - - Argument() { call.getArgument(pos) = this } - - /** Gets the call that has this argument. */ - Call getCall() { result = call } - - /** Gets the position of this argument. */ - int getPosition() { result = pos } -} - -newtype TDataFlowCallable = - TDataFlowPredicate(Predicate p) or - TDataFlowTopLevel() or - TDataFlowNewtypeBranch(NewTypeBranch branch) - -class DataFlowCallable extends TDataFlowCallable { - string toString() { - exists(Predicate p | - this = TDataFlowPredicate(p) and - result = p.toString() - ) - or - this = TDataFlowTopLevel() and - result = "top level" - or - exists(NewTypeBranch branch | - this = TDataFlowNewtypeBranch(branch) and - result = branch.toString() - ) - } - - Predicate asPredicate() { this = TDataFlowPredicate(result) } - - predicate asTopLevel() { this = TDataFlowTopLevel() } - - NewTypeBranch asNewTypeBranch() { this = TDataFlowNewtypeBranch(result) } -} - -private newtype TParameter = - TThisParam(ClassPredicate p) or - TResultParam(Predicate p) { exists(p.getReturnType()) } or - TVarParam(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v } - -class Parameter extends TParameter { - string toString() { this.hasName(result) } - - predicate hasName(string name) { - this instanceof TThisParam and name = "this" - or - this instanceof TResultParam and name = "result" - or - exists(VarDecl v | this = TVarParam(v, _, _) and name = v.toString()) - } - - int getIndex() { - this instanceof TThisParam and result = -1 - or - this instanceof TResultParam and result = -2 - or - this = TVarParam(_, result, _) - } - - Predicate getPredicate() { - this = TThisParam(result) - or - this = TResultParam(result) - or - this = TVarParam(_, _, result) - } - - Type getType() { - exists(ClassPredicate cp | - this = TThisParam(cp) and - result = cp.getDeclaringType() - ) - or - exists(Predicate p | - this = TResultParam(p) and - result = p.getReturnType() - ) - or - exists(VarDecl v | - this = TVarParam(v, _, _) and - result = v.getType() - ) - } - - Location getLocation() { - exists(ClassPredicate cp | - this = TThisParam(cp) and - result = cp.getLocation() - ) - or - exists(Predicate p | - this = TResultParam(p) and - result = p.getLocation() - ) - or - exists(VarDecl v | - this = TVarParam(v, _, _) and - result = v.getLocation() - ) - } -} - -/** - * A node in a data flow graph. - * - * A node can be either an expression, a parameter, or an uninitialized local - * variable. Such nodes are created with `DataFlow::exprNode`, - * `DataFlow::parameterNode`, and `DataFlow::uninitializedNode` respectively. - */ -class Node extends TNode { - /** Gets the function to which this node belongs. */ - DataFlowCallable getFunction() { none() } // overridden in subclasses - - /** - * INTERNAL: Do not use. Alternative name for `getFunction`. - */ - final DataFlowCallable getEnclosingCallable() { result = this.getFunction() } - - /** Gets the type of this node. */ - Type getType() { none() } // overridden in subclasses - - /** - * Gets the expression corresponding to this node, if any. This predicate - * only has a result on nodes that represent the value of evaluating the - * expression. For data flowing _out of_ an expression, like when an - * argument is passed by reference, use `asDefiningArgument` instead of - * `asExpr`. - */ - Expr asExpr() { result = this.(ExprNode).getExpr() } - - /** Gets the parameter corresponding to this node, if any. */ - Parameter asParameter() { result = this.(ParameterNode).getParameter() } - - /** Gets a textual representation of this element. */ - string toString() { none() } // overridden by subclasses - - /** Gets the location of this element. */ - Location getLocation() { none() } // overridden by subclasses - - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - /** - * Gets an upper bound on the type of this node. - */ - Type getTypeBound() { result = getType() } -} - -NewTypeBranch getNewTypeBranch(Expr e) { e.getParent*() = result.getBody() } - -/** - * An expression, viewed as a node in a data flow graph. - */ -class ExprNode extends Node, TExprNode { - Expr expr; - - ExprNode() { this = TExprNode(expr) } - - override DataFlowCallable getFunction() { - result.asPredicate() = expr.getEnclosingPredicate() - or - result.asNewTypeBranch() = getNewTypeBranch(expr) - or - not exists(expr.getEnclosingPredicate()) and - not exists(getNewTypeBranch(expr)) and - result.asTopLevel() - } - - override Type getType() { result = expr.getType() } - - override string toString() { result = expr.toString() } - - override Location getLocation() { result = expr.getLocation() } - - /** Gets the expression corresponding to this node. */ - Expr getExpr() { result = expr } -} - -ExprNode exprNode(Expr e) { result.getExpr() = e } - -class ParameterNode extends Node, TParameterNode { - Parameter p; - - ParameterNode() { this = TParameterNode(p) } - - Parameter getParameter() { result = p } - - /** - * Holds if this node is the parameter of `c` at the specified (zero-based) - * position. The implicit `this` parameter is considered to have index `-1`. - */ - predicate isParameterOf(DataFlowCallable pred, int i) { - p.getPredicate() = pred.asPredicate() and - p.getIndex() = i - } - - override DataFlowCallable getFunction() { result.asPredicate() = p.getPredicate() } - - override Type getType() { result = p.getType() } - - override string toString() { result = p.toString() } - - override Location getLocation() { result = p.getLocation() } -} - -/** A data flow node that represents a returned value in the called function. */ -abstract class ReturnNode extends Node { - /** Gets the kind of this returned value. */ - abstract ReturnKind getKind(); -} - -newtype TReturnKind = - TNormalReturnKind() or - TParameterOutKind(int i) { any(Parameter p).getIndex() = i } - -/** - * A return kind. A return kind describes how a value can be returned - * from a callable. For C++, this is simply a function return. - */ -class ReturnKind extends TReturnKind { - /** Gets a textual representation of this return kind. */ - string toString() { - this instanceof TNormalReturnKind and - result = "return" - or - exists(int i | - this = TParameterOutKind(i) and - result = "out(" + i + ")" - ) - } -} - -/** A data flow node that represents the output of a call at the call site. */ -abstract class OutNode extends Node { - /** Gets the underlying call. */ - abstract Call getCall(); -} - -class ArgumentOutNode extends Node, TArgumentOutNode, OutNode { - VarAccess acc; - Call call; - int pos; - - ArgumentOutNode() { this = TArgumentOutNode(acc, call, pos) } - - override DataFlowCallable getFunction() { - result.asPredicate() = acc.getEnclosingPredicate() - or - result.asNewTypeBranch() = getNewTypeBranch(acc) - or - not exists(acc.getEnclosingPredicate()) and - not exists(getNewTypeBranch(acc)) and - result.asTopLevel() - } - - VarAccess getVarAccess() { result = acc } - - override Type getType() { result = acc.getType() } - - override string toString() { result = acc.toString() + " [out]" } - - override Location getLocation() { result = acc.getLocation() } - - override Call getCall() { result = call } - - int getIndex() { result = pos } -} - -class OutParameterNode extends Node, ReturnNode, TOutParameterNode { - Parameter p; - - OutParameterNode() { this = TOutParameterNode(p) } - - Parameter getParameter() { result = p } - - /** - * Holds if this node is the parameter of `c` at the specified (zero-based) - * position. The implicit `this` parameter is considered to have index `-1`. - */ - predicate isParameterOf(DataFlowCallable pred, int i) { - p.getPredicate() = pred.asPredicate() and - p.getIndex() = i - } - - override DataFlowCallable getFunction() { result.asPredicate() = p.getPredicate() } - - override Type getType() { result = p.getType() } - - override string toString() { result = p.toString() } - - override Location getLocation() { result = p.getLocation() } - - override ReturnKind getKind() { result = TParameterOutKind(p.getIndex()) } -} - -/** - * A node associated with an object after an operation that might have - * changed its state. - * - * This can be either the argument to a callable after the callable returns - * (which might have mutated the argument), or the qualifier of a field after - * an update to the field. - * - * Nodes corresponding to AST elements, for example `ExprNode`, usually refer - * to the value before the update with the exception of `ClassInstanceExpr`, - * which represents the value after the constructor has run. - */ -abstract class PostUpdateNode extends Node { - /** - * Gets the node before the state update. - */ - abstract Node getPreUpdateNode(); - - override DataFlowCallable getFunction() { result = getPreUpdateNode().getFunction() } - - override Type getType() { result = getPreUpdateNode().getType() } - - override Location getLocation() { result = getPreUpdateNode().getLocation() } -} - -/** - * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local - * (intra-procedural) step. - */ -cached -predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFrom, nodeTo) } - -AstNode getParentOfExpr(Expr e) { result = e.getParent() } - -Formula getEnclosing(Expr e) { result = getParentOfExpr+(e) } - -Formula enlargeScopeStep(Formula f) { result.(Conjunction).getAnOperand() = f } - -Formula enlargeScope(Formula f) { - result = enlargeScopeStep*(f) and not exists(enlargeScopeStep(result)) -} - -predicate varaccesValue(VarAccess va, VarDecl v, Formula scope) { - va.getDeclaration() = v and - scope = enlargeScope(getEnclosing(va)) -} - -predicate thisValue(ThisAccess ta, Formula scope) { scope = enlargeScope(getEnclosing(ta)) } - -predicate resultValue(ResultAccess ra, Formula scope) { scope = enlargeScope(getEnclosing(ra)) } - -Formula getParentFormula(Formula f) { f.getParent() = result } - -predicate valueStep(Expr e1, Expr e2) { - exists(VarDecl v, Formula scope | - varaccesValue(e1, v, scope) and - varaccesValue(e2, v, scope) - ) - or - exists(VarDecl v, Formula f, Select sel | - getParentFormula*(f) = sel.getWhere() and - varaccesValue(e1, v, f) and - sel.getExpr(_) = e2 - ) - or - exists(Formula scope | - thisValue(e1, scope) and - thisValue(e2, scope) - or - resultValue(e1, scope) and - resultValue(e2, scope) - ) - or - exists(InlineCast c | - e1 = c and e2 = c.getBase() - or - e2 = c and e1 = c.getBase() - ) - or - exists(ComparisonFormula eq | - eq.getSymbol() = "=" and - eq.getAnOperand() = e1 and - eq.getAnOperand() = e2 and - e1 != e2 - ) -} - -predicate paramStep(Expr e1, Parameter p2) { - exists(VarDecl v | - p2 = TVarParam(v, _, _) and - varaccesValue(e1, v, _) - ) - or - exists(Formula scope | - p2 = TThisParam(scope.getEnclosingPredicate()) and - thisValue(e1, scope) - or - p2 = TResultParam(scope.getEnclosingPredicate()) and - resultValue(e1, scope) - ) -} - -/** - * INTERNAL: do not use. - * - * This is the local flow predicate that's used as a building block in global - * data flow. It may have less flow than the `localFlowStep` predicate. - */ -predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { - valueStep(nodeFrom.asExpr(), nodeTo.asExpr()) - or - paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter()) - or - valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr()) -} - -predicate foo(Node nodeFrom, Node nodeTo, Select sel) { - valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr()) and - sel.getExpr(_) = nodeTo.asExpr() and - nodeFrom.getLocation().getFile().getBaseName() = "OverflowStatic.ql" and - nodeFrom.getLocation().getStartLine() = 147 - // paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter()) and - // nodeFrom.getLocation().getStartLine() = 60 and - // nodeFrom.getLocation().getFile().getBaseName() = "OverflowStatic.ql" -} - -/** - * Holds if data flows from `source` to `sink` in zero or more local - * (intra-procedural) steps. - */ -predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) } - -private newtype TContent = - TFieldContent() or - TCollectionContent() or - TArrayContent() - -/** - * A description of the way data may be stored inside an object. Examples - * include instance fields, the contents of a collection object, or the contents - * of an array. - */ -class Content extends TContent { - /** Gets a textual representation of this element. */ - abstract string toString(); - - predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { - path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 - } -} - -/** A reference through an instance field. */ -class FieldContent extends Content, TFieldContent { - override string toString() { result = "" } -} - -/** A reference through an array. */ -private class ArrayContent extends Content, TArrayContent { - override string toString() { result = "[]" } -} - -/** A reference through the contents of some collection-like container. */ -private class CollectionContent extends Content, TCollectionContent { - override string toString() { result = "" } -} - -class GuardCondition extends Formula { - GuardCondition() { any(IfFormula ifFormula).getCondition() = this } -} - -/** - * A guard that validates some expression. - * - * To use this in a configuration, extend the class and provide a - * characteristic predicate precisely specifying the guard, and override - * `checks` to specify what is being validated and in which branch. - * - * It is important that all extending classes in scope are disjoint. - */ -class BarrierGuard extends GuardCondition { - /** Override this predicate to hold if this guard validates `e` upon evaluating to `b`. */ - abstract predicate checks(Expr e, boolean b); - - /** Gets a node guarded by this guard. */ - final ExprNode getAGuardedNode() { none() } -} diff --git a/ql/src/queries/style/ToStringInQueryLogic.ql b/ql/src/queries/style/ToStringInQueryLogic.ql index 33558105e53..bdb9217124e 100644 --- a/ql/src/queries/style/ToStringInQueryLogic.ql +++ b/ql/src/queries/style/ToStringInQueryLogic.ql @@ -10,7 +10,6 @@ import ql import codeql_ql.ast.internal.Builtins -import codeql.dataflow.DataFlow class ToString extends Predicate { ToString() { this.getName() = "toString" } @@ -36,25 +35,209 @@ class RegexpReplaceAllCall extends MemberCall { RegexpReplaceAllCall() { this.getTarget() instanceof RegexpReplaceAll } } -class ToSelectConf extends DataFlow::Configuration { - ToSelectConf() { this = "ToSelectConf" } +module DataFlow { + private newtype TNode = + TExprNode(Expr e) or + TOutParameterNode(Parameter p) or + TArgumentOutNode(VarAccess acc, Call call, int pos) { + acc.(Argument).getCall() = call and acc.(Argument).getPosition() = pos + } or + TParameterNode(Parameter p) - override predicate isSource(DataFlow::Node source) { - exists(ToStringCall toString | - source.asExpr() = toString and - not toString.getEnclosingPredicate() instanceof ToString + /** An argument to a call. */ + class Argument extends Expr { + Call call; + int pos; + + Argument() { call.getArgument(pos) = this } + + /** Gets the call that has this argument. */ + Call getCall() { result = call } + + /** Gets the position of this argument. */ + int getPosition() { result = pos } + } + + private newtype TParameter = + TThisParam(ClassPredicate p) or + TResultParam(Predicate p) { exists(p.getReturnType()) } or + TVarParam(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v } + + class Parameter extends TParameter { + string toString() { this.hasName(result) } + + ClassPredicate asThis() { this = TThisParam(result) } + + Predicate asResult() { this = TResultParam(result) } + + VarDecl asVar(int i, Predicate pred) { this = TVarParam(result, i, pred) } + + predicate hasName(string name) { + exists(this.asThis()) and name = "this" + or + exists(this.asResult()) and name = "result" + or + name = this.asVar(_, _).getName() + } + + int getIndex() { + exists(this.asThis()) and result = -1 + or + exists(this.asResult()) and result = -2 + or + exists(this.asVar(result, _)) + } + + Predicate getPredicate() { + result = this.asThis() + or + result = this.asResult() + or + exists(this.asVar(_, result)) + } + } + + class Node extends TNode { + /** Gets the predicate to which this node belongs. */ + Predicate getPredicate() { none() } // overridden in subclasses + + Expr asExpr() { result = this.(ExprNode).getExpr() } + + /** Gets a textual representation of this element. */ + string toString() { none() } // overridden by subclasses + } + + /** + * An expression, viewed as a node in a data flow graph. + */ + class ExprNode extends Node, TExprNode { + Expr expr; + + ExprNode() { this = TExprNode(expr) } + + override string toString() { result = expr.toString() } + + /** Gets the expression corresponding to this node. */ + Expr getExpr() { result = expr } + } + + class ParameterNode extends Node, TParameterNode { + Parameter p; + + ParameterNode() { this = TParameterNode(p) } + + override string toString() { result = p.toString() } + } + + newtype TReturnKind = + TNormalReturnKind() or + TParameterOutKind(int i) { any(Parameter p).getIndex() = i } + + /** A data flow node that represents the output of a call at the call site. */ + abstract class OutNode extends Node { + /** Gets the underlying call. */ + abstract Call getCall(); + } + + class ArgumentOutNode extends Node, TArgumentOutNode, OutNode { + VarAccess acc; + Call call; + int pos; + + ArgumentOutNode() { this = TArgumentOutNode(acc, call, pos) } + + VarAccess getVarAccess() { result = acc } + + override string toString() { result = acc.toString() + " [out]" } + + override Call getCall() { result = call } + + int getIndex() { result = pos } + } + + class OutParameterNode extends Node, TOutParameterNode { + Parameter p; + + OutParameterNode() { this = TOutParameterNode(p) } + + Parameter getParameter() { result = p } + + override string toString() { result = p.toString() } + } + + AstNode getParentOfExpr(Expr e) { result = e.getParent() } + + Formula getEnclosing(Expr e) { result = getParentOfExpr+(e) } + + Formula enlargeScopeStep(Formula f) { result.(Conjunction).getAnOperand() = f } + + Formula enlargeScope(Formula f) { + result = enlargeScopeStep*(f) and not exists(enlargeScopeStep(result)) + } + + predicate varaccesValue(VarAccess va, VarDecl v, Formula scope) { + va.getDeclaration() = v and + scope = enlargeScope(getEnclosing(va)) + } + + predicate thisValue(ThisAccess ta, Formula scope) { scope = enlargeScope(getEnclosing(ta)) } + + predicate resultValue(ResultAccess ra, Formula scope) { scope = enlargeScope(getEnclosing(ra)) } + + Formula getParentFormula(Formula f) { f.getParent() = result } + + predicate valueStep(Expr e1, Expr e2) { + exists(VarDecl v, Formula scope | + varaccesValue(e1, v, scope) and + varaccesValue(e2, v, scope) + ) + or + exists(VarDecl v, Formula f, Select sel | + getParentFormula*(f) = sel.getWhere() and + varaccesValue(e1, v, f) and + sel.getExpr(_) = e2 + ) + or + exists(Formula scope | + thisValue(e1, scope) and + thisValue(e2, scope) + or + resultValue(e1, scope) and + resultValue(e2, scope) + ) + or + exists(InlineCast c | + e1 = c and e2 = c.getBase() + or + e2 = c and e1 = c.getBase() + ) + or + exists(ComparisonFormula eq | + eq.getSymbol() = "=" and + eq.getAnOperand() = e1 and + eq.getAnOperand() = e2 and + e1 != e2 ) } - override predicate isSink(DataFlow::Node sink) { - sink.asExpr() = any(Select s).getExpr(_) or - sink.getEnclosingCallable().asPredicate() instanceof NodesPredicate or - sink.getEnclosingCallable().asPredicate() instanceof EdgesPredicate + predicate paramStep(Expr e1, Parameter p2) { + exists(VarDecl v | + p2 = TVarParam(v, _, _) and + varaccesValue(e1, v, _) + ) + or + exists(Formula scope | + p2 = TThisParam(scope.getEnclosingPredicate()) and + thisValue(e1, scope) + or + p2 = TResultParam(scope.getEnclosingPredicate()) and + resultValue(e1, scope) + ) } - override predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - nodeFrom.getType() instanceof StringClass and - nodeTo.getType() instanceof StringClass and + predicate additionalLocalStep(Node nodeFrom, Node nodeTo) { + nodeFrom.asExpr().getType() instanceof StringClass and + nodeFrom.asExpr().getType() instanceof StringClass and exists(BinOpExpr binop | nodeFrom.asExpr() = binop.getAnOperand() and nodeTo.asExpr() = binop @@ -62,12 +245,65 @@ class ToSelectConf extends DataFlow::Configuration { or nodeTo.asExpr().(RegexpReplaceAllCall).getBase() = nodeFrom.asExpr() } + + predicate localStep(Node nodeFrom, Node nodeTo) { + valueStep(nodeFrom.asExpr(), nodeTo.asExpr()) + or + paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter()) + or + valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr()) + or + additionalLocalStep(nodeFrom, nodeTo) + } + + predicate step(Node nodeFrom, Node nodeTo) { + // Local flow + localStep(nodeFrom, nodeTo) + or + // Flow out of functions + exists(Call call, Parameter p, OutParameterNode outParam, ArgumentOutNode outArg | + outParam = nodeFrom and + outArg = nodeTo + | + p = outParam.getParameter() and + p.getPredicate() = call.getTarget() and + outArg.getCall() = call and + outArg.getIndex() = p.getIndex() + ) + } + + predicate flowsFromSource(Node node) { + isSource(node) + or + exists(Node mid | flowsFromSource(mid) | step(mid, node)) + } + + predicate flowsToSink(Node node) { + flowsFromSource(node) and isSink(node) + or + exists(Node mid | flowsToSink(mid) | step(node, mid)) + } + + predicate isSink(Node sink) { + sink.asExpr() = any(Select s).getExpr(_) + or + sink.getPredicate() instanceof NodesPredicate + or + sink.getPredicate() instanceof EdgesPredicate + } + + predicate isSource(Node source) { + exists(ToStringCall toString | + source.asExpr() = toString and + not toString.getEnclosingPredicate() instanceof ToString + ) + } } predicate flowsToSelect(Expr e) { exists(DataFlow::Node source | source.asExpr() = e and - any(ToSelectConf conf).hasFlow(source, _) + DataFlow::flowsToSink(source) ) } From 5b261d88bb66bf4573c115b8f28b16bcb6039c21 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 15 Oct 2021 12:26:11 +0000 Subject: [PATCH 42/43] Support `super` with `instanceof` --- ql/src/codeql_ql/ast/Ast.qll | 5 +++++ ql/src/codeql_ql/ast/internal/Predicate.qll | 5 +++-- ql/src/codeql_ql/ast/internal/Type.qll | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 14cdb86764e..bb819b59a53 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -754,6 +754,11 @@ class Class extends TClass, TypeDeclaration, ModuleDeclaration { */ TypeExpr getASuperType() { toQL(result) = cls.getExtends(_) } + /** + * Gets a type referenced in the `instanceof` part of the class declaration. + */ + TypeExpr getAnInstanceofType() { toQL(result) = cls.getInstanceof(_) } + /** Gets the type that this class is defined to be an alias of. */ TypeExpr getAliasType() { toQL(result) = cls.getChild(_).(QL::TypeAliasBody).getChild() } diff --git a/ql/src/codeql_ql/ast/internal/Predicate.qll b/ql/src/codeql_ql/ast/internal/Predicate.qll index 31c91c7ae6d..5fc84f6f96d 100644 --- a/ql/src/codeql_ql/ast/internal/Predicate.qll +++ b/ql/src/codeql_ql/ast/internal/Predicate.qll @@ -79,10 +79,11 @@ private module Cached { ) or // super calls - exists(Super sup, ClassType type | + exists(Super sup, ClassType type, Type supertype | mc.getBase() = sup and sup.getEnclosingPredicate().(ClassPredicate).getParent().getType() = type and - p = type.getASuperType().getClassPredicate(mc.getMemberName(), mc.getNumberOfArguments()) + supertype in [type.getASuperType(), type.getAnInstanceofType()] and + p = supertype.getClassPredicate(mc.getMemberName(), mc.getNumberOfArguments()) ) } diff --git a/ql/src/codeql_ql/ast/internal/Type.qll b/ql/src/codeql_ql/ast/internal/Type.qll index 8449b08de4c..bfd84ea9208 100644 --- a/ql/src/codeql_ql/ast/internal/Type.qll +++ b/ql/src/codeql_ql/ast/internal/Type.qll @@ -94,6 +94,8 @@ class ClassType extends Type, TClass { override Type getASuperType() { result = decl.getASuperType().getResolvedType() } + Type getAnInstanceofType() { result = decl.getAnInstanceofType().getResolvedType() } + override Type getAnInternalSuperType() { result.(ClassCharType).getClassType() = this or From f5bb1d012457fa86db89b963b6d3ffa2dd23bebd Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 15 Oct 2021 16:02:23 +0100 Subject: [PATCH 43/43] QL: Respond to PR reviews. --- ql/src/queries/style/ToStringInQueryLogic.ql | 108 +++++++++++-------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/ql/src/queries/style/ToStringInQueryLogic.ql b/ql/src/queries/style/ToStringInQueryLogic.ql index bdb9217124e..93a4b816475 100644 --- a/ql/src/queries/style/ToStringInQueryLogic.ql +++ b/ql/src/queries/style/ToStringInQueryLogic.ql @@ -59,42 +59,56 @@ module DataFlow { } private newtype TParameter = - TThisParam(ClassPredicate p) or - TResultParam(Predicate p) { exists(p.getReturnType()) } or - TVarParam(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v } + TThisParameter(ClassPredicate p) or + TResultParameter(Predicate p) { exists(p.getReturnType()) } or + TVariableParameter(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v } - class Parameter extends TParameter { + abstract class Parameter extends TParameter { string toString() { this.hasName(result) } - ClassPredicate asThis() { this = TThisParam(result) } + abstract predicate hasName(string name); - Predicate asResult() { this = TResultParam(result) } + abstract int getIndex(); - VarDecl asVar(int i, Predicate pred) { this = TVarParam(result, i, pred) } + abstract Predicate getPredicate(); + } - predicate hasName(string name) { - exists(this.asThis()) and name = "this" - or - exists(this.asResult()) and name = "result" - or - name = this.asVar(_, _).getName() - } + class ThisParameter extends Parameter, TThisParameter { + ClassPredicate p; - int getIndex() { - exists(this.asThis()) and result = -1 - or - exists(this.asResult()) and result = -2 - or - exists(this.asVar(result, _)) - } + ThisParameter() { this = TThisParameter(p) } - Predicate getPredicate() { - result = this.asThis() - or - result = this.asResult() - or - exists(this.asVar(_, result)) - } + override predicate hasName(string name) { name = "this" } + + override int getIndex() { result = -1 } + + override Predicate getPredicate() { result = p } + } + + class ResultParameter extends Parameter, TResultParameter { + Predicate p; + + ResultParameter() { this = TResultParameter(p) } + + override predicate hasName(string name) { name = "result" } + + override int getIndex() { result = -2 } + + override Predicate getPredicate() { result = p } + } + + class VariableParameter extends Parameter, TVariableParameter { + VarDecl v; + int i; + Predicate p; + + VariableParameter() { this = TVariableParameter(v, i, p) } + + override predicate hasName(string name) { name = v.getName() } + + override int getIndex() { result = i } + + override Predicate getPredicate() { result = p } } class Node extends TNode { @@ -222,15 +236,15 @@ module DataFlow { predicate paramStep(Expr e1, Parameter p2) { exists(VarDecl v | - p2 = TVarParam(v, _, _) and + p2 = TVariableParameter(v, _, _) and varaccesValue(e1, v, _) ) or exists(Formula scope | - p2 = TThisParam(scope.getEnclosingPredicate()) and + p2 = TThisParameter(scope.getEnclosingPredicate()) and thisValue(e1, scope) or - p2 = TResultParam(scope.getEnclosingPredicate()) and + p2 = TResultParameter(scope.getEnclosingPredicate()) and resultValue(e1, scope) ) } @@ -273,7 +287,7 @@ module DataFlow { } predicate flowsFromSource(Node node) { - isSource(node) + isSource(node.asExpr()) or exists(Node mid | flowsFromSource(mid) | step(mid, node)) } @@ -292,11 +306,10 @@ module DataFlow { sink.getPredicate() instanceof EdgesPredicate } - predicate isSource(Node source) { - exists(ToStringCall toString | - source.asExpr() = toString and - not toString.getEnclosingPredicate() instanceof ToString - ) + predicate isSource(ToStringCall toString) { + not toString.getEnclosingPredicate() instanceof ToString and + not toString.getEnclosingPredicate() instanceof NodesPredicate and + not toString.getEnclosingPredicate() instanceof EdgesPredicate } } @@ -309,16 +322,23 @@ predicate flowsToSelect(Expr e) { from ToStringCall call where + // It's not part of a toString call + DataFlow::isSource(call) and // The call doesn't flow to a select not flowsToSelect(call) and - // It's not part of a toString call - not call.getEnclosingPredicate() instanceof ToString and // It's in a query call.getLocation().getFile().getBaseName().matches("%.ql") and // ... and not in a test - not call.getLocation() - .getFile() - .getAbsolutePath() - .toLowerCase() - .matches(["%test%", "%consistency%", "%meta%"]) + not ( + call.getLocation() + .getFile() + .getAbsolutePath() + .toLowerCase() + .matches(["%test%", "%consistency%", "%meta%"]) + or + call.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + ) select call, "Query logic depends on implementation of 'toString'."