diff --git a/.codeqlmanifest.json b/.codeqlmanifest.json index f14b408a83ce..0767ce6ffff7 100644 --- a/.codeqlmanifest.json +++ b/.codeqlmanifest.json @@ -11,7 +11,9 @@ "misc/legacy-support/*/qlpack.yml", "misc/suite-helpers/qlpack.yml", "ruby/extractor-pack/codeql-extractor.yml", - "ruby/ql/consistency-queries/qlpack.yml" + "ruby/ql/consistency-queries/qlpack.yml", + "ql/ql/consistency-queries/qlpack.yml", + "ql/extractor-pack/codeql-extractor.yml" ], "versionPolicies": { "default": { diff --git a/.github/workflows/ql-for-ql-build.yml b/.github/workflows/ql-for-ql-build.yml new file mode 100644 index 000000000000..062ddb2b6d6d --- /dev/null +++ b/.github/workflows/ql-for-ql-build.yml @@ -0,0 +1,152 @@ +name: Run QL for QL + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + queries: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Find codeql + id: find-codeql + uses: github/codeql-action/init@esbena/ql + with: + languages: javascript # does not matter + - name: Get CodeQL version + id: get-codeql-version + run: | + echo "::set-output name=version::$("${CODEQL}" --version | head -n 1 | rev | cut -d " " -f 1 | rev)" + shell: bash + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - name: Cache queries + id: cache-queries + uses: actions/cache@v2 + with: + path: ${{ runner.temp }}/query-pack.zip + key: queries-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }} + - name: Build query pack + if: steps.cache-queries.outputs.cache-hit != 'true' + run: | + cd ql/ql/src + "${CODEQL}" pack create + cd .codeql/pack/codeql/ql-all/0.0.0 + zip "${PACKZIP}" -r . + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + PACKZIP: ${{ runner.temp }}/query-pack.zip + - name: Upload query pack + uses: actions/upload-artifact@v2 + with: + name: query-pack-zip + path: ${{ runner.temp }}/query-pack.zip + + extractors: + strategy: + fail-fast: false + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ql/target + key: ${{ runner.os }}-rust-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Check formatting + run: cd ql; cargo fmt --all -- --check + - name: Build + run: cd ql; cargo build --verbose + - name: Run tests + run: cd ql; cargo test --verbose + - name: Release build + run: cd ql; cargo build --release + - name: Generate dbscheme + run: ql/target/release/ql-generator --dbscheme ql/ql/src/ql.dbscheme --library ql/ql/src/codeql_ql/ast/internal/TreeSitter.qll + - uses: actions/upload-artifact@v2 + with: + name: extractor-ubuntu-latest + path: | + ql/target/release/ql-extractor + ql/target/release/ql-extractor.exe + retention-days: 1 + package: + runs-on: ubuntu-latest + + needs: + - extractors + - queries + + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2 + with: + name: query-pack-zip + path: query-pack-zip + - uses: actions/download-artifact@v2 + with: + name: extractor-ubuntu-latest + path: linux64 + - run: | + unzip query-pack-zip/*.zip -d pack + cp -r ql/codeql-extractor.yml ql/tools ql/ql/src/ql.dbscheme.stats pack/ + mkdir -p pack/tools/linux64 + if [[ -f linux64/ql-extractor ]]; then + cp linux64/ql-extractor pack/tools/linux64/extractor + chmod +x pack/tools/linux64/extractor + fi + cd pack + zip -rq ../codeql-ql.zip . + - uses: actions/upload-artifact@v2 + with: + name: codeql-ql-pack + path: codeql-ql.zip + retention-days: 1 + analyze: + runs-on: ubuntu-latest + + needs: + - package + + steps: + - name: Download pack + uses: actions/download-artifact@v2 + with: + name: codeql-ql-pack + path: ${{ runner.temp }}/codeql-ql-pack-artifact + + - name: Prepare pack + run: | + unzip "${PACK_ARTIFACT}/*.zip" -d "${PACK}" + env: + PACK_ARTIFACT: ${{ runner.temp }}/codeql-ql-pack-artifact + PACK: ${{ runner.temp }}/pack + - 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 }}/pack + + - name: Checkout repository + uses: actions/checkout@v2 + + - 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 + diff --git a/.github/workflows/ql-for-ql-dataset_measure.yml b/.github/workflows/ql-for-ql-dataset_measure.yml new file mode 100644 index 000000000000..665ccb102938 --- /dev/null +++ b/.github/workflows/ql-for-ql-dataset_measure.yml @@ -0,0 +1,84 @@ +name: Collect database stats for QL for QL + +on: + push: + branches: [main] + paths: + - ql/ql/src/ql.dbscheme + pull_request: + branches: [main] + paths: + - ql/ql/src/ql.dbscheme + workflow_dispatch: + +jobs: + measure: + env: + CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI + strategy: + matrix: + repo: + - github/codeql + - github/codeql-go + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Find codeql + id: find-codeql + uses: github/codeql-action/init@esbena/ql + with: + languages: javascript # does not matter + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ql/target + key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Build Extractor + run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./create-extractor-pack.sh + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - name: Checkout ${{ matrix.repo }} + uses: actions/checkout@v2 + with: + repository: ${{ matrix.repo }} + path: ${{ github.workspace }}/repo + - name: Create database + run: | + "${CODEQL}" database create \ + --search-path "ql/extractor-pack" \ + --threads 4 \ + --language ql --source-root "${{ github.workspace }}/repo" \ + "${{ runner.temp }}/database" + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - name: Measure database + run: | + mkdir -p "stats/${{ matrix.repo }}" + "${CODEQL}" dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ql" + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - uses: actions/upload-artifact@v2 + with: + name: measurements + path: stats + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: measure + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2 + with: + name: measurements + path: stats + - run: | + python -m pip install --user lxml + find stats -name 'stats.xml' -print0 | sort -z | xargs -0 python ql/scripts/merge_stats.py --output ql/ql/src/ql.dbscheme.stats --normalise ql_tokeninfo + - uses: actions/upload-artifact@v2 + with: + name: ql.dbscheme.stats + path: ql/ql/src/ql.dbscheme.stats diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml new file mode 100644 index 000000000000..9fada8ad3611 --- /dev/null +++ b/.github/workflows/ql-for-ql-tests.yml @@ -0,0 +1,52 @@ +name: Run QL for QL Tests + +on: + push: + branches: [main] + paths: + - ql/* + pull_request: + branches: [main] + paths: + - ql/* + +env: + CARGO_TERM_COLOR: always + +jobs: + qltest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Find codeql + id: find-codeql + uses: github/codeql-action/init@esbena/ql + with: + languages: javascript # does not matter + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ql/target + key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Build extractor + run: | + cd ql; + codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }}); + env "PATH=$PATH:$codeqlpath" ./create-extractor-pack.sh + - name: Run QL tests + run: | + "${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - name: Check QL formatting + run: | + find ql/ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} + - name: Check QL compilation + run: | + "${CODEQL}" query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ql/extractor-pack" "ql/ql/src" "ql/ql/examples" + env: + CODEQL: ${{ steps.find-codeql.outputs.codeql-path }} diff --git a/CODEOWNERS b/CODEOWNERS index d5f3362a7ca6..3c781c5bf131 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -25,3 +25,6 @@ /docs/codeql-for-visual-studio-code/ @github/codeql-vscode-reviewers /docs/ql-language-reference/ @github/codeql-frontend-reviewers /docs/query-*-style-guide.md @github/codeql-analysis-reviewers + +# QL for QL reviewers +/ql/ @erik-krogh @tausbn \ No newline at end of file diff --git a/ql/.gitattributes b/ql/.gitattributes new file mode 100644 index 000000000000..f4c10fb0a918 --- /dev/null +++ b/ql/.gitattributes @@ -0,0 +1 @@ +Cargo.lock -diff -whitespace diff --git a/ql/.gitignore b/ql/.gitignore new file mode 100644 index 000000000000..5af0736b59f8 --- /dev/null +++ b/ql/.gitignore @@ -0,0 +1,8 @@ +target +extractor-pack +.vscode/launch.json +.cache +ql/test/**/*.testproj +ql/test/**/*.actual +ql/test/**/CONSISTENCY +work diff --git a/ql/.vscode/tasks.json b/ql/.vscode/tasks.json new file mode 100644 index 000000000000..c28cd789fcae --- /dev/null +++ b/ql/.vscode/tasks.json @@ -0,0 +1,14 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "subcommand": "build", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "Rust: cargo build" + } + ] +} \ No newline at end of file diff --git a/ql/Cargo.lock b/ql/Cargo.lock new file mode 100644 index 000000000000..16626b5d8362 --- /dev/null +++ b/ql/Cargo.lock @@ -0,0 +1,643 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + +[[package]] +name = "cc" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const_fn" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +dependencies = [ + "cfg-if", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "node-types" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + +[[package]] +name = "pin-project-lite" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "ql-extractor" +version = "0.1.0" +dependencies = [ + "clap", + "flate2", + "node-types", + "num_cpus", + "rayon", + "regex", + "tracing", + "tracing-subscriber", + "tree-sitter", + "tree-sitter-ql", +] + +[[package]] +name = "ql-generator" +version = "0.1.0" +dependencies = [ + "clap", + "node-types", + "tracing", + "tracing-subscriber", + "tree-sitter-ql", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tracing" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f080ea7e4107844ef4766459426fa2d5c1ada2e47edba05dc7fa99d9629f47" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-log" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" +dependencies = [ + "ansi_term 0.12.1", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tree-sitter" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad726ec26496bf4c083fff0f43d4eb3a2ad1bba305323af5ff91383c0b6ecac0" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-ql" +version = "0.19.0" +source = "git+https://github.com/tausbn/tree-sitter-ql.git?rev=36bdc0eae196f9833182ce3f8932be63534121b3#36bdc0eae196f9833182ce3f8932be63534121b3" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/ql/Cargo.toml b/ql/Cargo.toml new file mode 100644 index 000000000000..fe2bfbea7ac1 --- /dev/null +++ b/ql/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "extractor", + "generator", + "node-types", +] diff --git a/ql/README.md b/ql/README.md new file mode 100644 index 000000000000..4bb5e30ba84f --- /dev/null +++ b/ql/README.md @@ -0,0 +1,50 @@ +# QL for QL + +QL for QL is a CodeQL analysis designed to find common bug patterns in QL code. +This analysis is mostly used as a PR check in [`github/codeql`](https://github.com/github/codeql). +QL for QL is experimental technology and not a supported product. + +This directory contains the extractor, CodeQL libraries, and queries that power QL for QL. + +Some setup is required to use QL for QL (see the below sections). + +## Building the tools from source + +[Install Rust](https://www.rust-lang.org/tools/install) (if using VSCode, you may also want the `rust-analyzer` extension), then run: + +```bash +cargo build --release +``` + +## Generating the database schema and QL library + +The generated `ql/src/ql.dbscheme` and `ql/src/codeql_ql/ast/internal/TreeSitter.qll` files are included in the repository, but they can be re-generated as follows: + +```bash +./create-extractor-pack.sh +``` + +## Building a CodeQL database for a QL program + +First, get an extractor pack: + +Run `./create-extractor-pack.sh` (Linux/Mac) or `.\create-extractor-pack.ps1` (Windows PowerShell) and the pack will be created in the `extractor-pack` directory. + +Then run + +```bash +codeql database create -l ql -s --search-path +``` + +CodeQL can be configured to remember the extractor by setting the config file `~/.config/codeql/config` to: +```bash +--search-path /full/path/to/extractor-pack +``` + +## Running qltests + +Run + +```bash +codeql test run --search-path +``` diff --git a/ql/codeql-extractor.yml b/ql/codeql-extractor.yml new file mode 100644 index 000000000000..4daf66134fce --- /dev/null +++ b/ql/codeql-extractor.yml @@ -0,0 +1,14 @@ +name: "ql" +display_name: "QL" +version: 0.0.1 +column_kind: "utf8" +legacy_qltest_extraction: true +file_types: + - name: ql + display_name: QL query files + extensions: + - .ql + - name: qll + display_name: QL library files + extensions: + - .qll diff --git a/ql/create-extractor-pack.ps1 b/ql/create-extractor-pack.ps1 new file mode 100644 index 000000000000..71e7beec72f9 --- /dev/null +++ b/ql/create-extractor-pack.ps1 @@ -0,0 +1,13 @@ +cargo build --release + +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 + +if (Test-Path -Path extractor-pack) { + rm -Recurse -Force extractor-pack +} +mkdir extractor-pack | Out-Null +cp codeql-extractor.yml, ql\src\ql.dbscheme, ql\src\ql.dbscheme.stats extractor-pack +cp -Recurse tools extractor-pack +mkdir extractor-pack\tools\win64 | Out-Null +cp target\release\ql-extractor.exe extractor-pack\tools\win64\extractor.exe diff --git a/ql/create-extractor-pack.sh b/ql/create-extractor-pack.sh new file mode 100755 index 000000000000..5e45c41f819d --- /dev/null +++ b/ql/create-extractor-pack.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -eux + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + platform="linux64" +elif [[ "$OSTYPE" == "darwin"* ]]; then + platform="osx64" +else + echo "Unknown OS" + exit 1 +fi + +cargo build --release + +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 +mkdir -p extractor-pack +cp -r codeql-extractor.yml tools ql/src/ql.dbscheme ql/src/ql.dbscheme.stats extractor-pack/ +mkdir -p extractor-pack/tools/${platform} +cp target/release/ql-extractor extractor-pack/tools/${platform}/extractor diff --git a/ql/extractor/Cargo.toml b/ql/extractor/Cargo.toml new file mode 100644 index 000000000000..3ccf1ce3430b --- /dev/null +++ b/ql/extractor/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ql-extractor" +version = "0.1.0" +authors = ["GitHub"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flate2 = "1.0" +node-types = { path = "../node-types" } +tree-sitter = "0.19" +tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "36bdc0eae196f9833182ce3f8932be63534121b3" } +clap = "2.33" +tracing = "0.1" +tracing-subscriber = { version = "0.2", features = ["env-filter"] } +rayon = "1.5.0" +num_cpus = "1.13.0" +regex = "1.4.3" diff --git a/ql/extractor/src/extractor.rs b/ql/extractor/src/extractor.rs new file mode 100644 index 000000000000..7c83f51e0dc5 --- /dev/null +++ b/ql/extractor/src/extractor.rs @@ -0,0 +1,829 @@ +use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName}; +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}; + +pub struct TrapWriter { + /// The accumulated trap entries + trap_output: Vec, + /// A counter for generating fresh labels + counter: u32, + /// cache of global keys + global_keys: std::collections::HashMap, +} + +pub fn new_trap_writer() -> TrapWriter { + TrapWriter { + counter: 0, + trap_output: Vec::new(), + global_keys: std::collections::HashMap::new(), + } +} + +impl TrapWriter { + /// Gets a label that will hold the unique ID of the passed string at import time. + /// This can be used for incrementally importable TRAP files -- use globally unique + /// strings to compute a unique ID for table tuples. + /// + /// Note: You probably want to make sure that the key strings that you use are disjoint + /// for disjoint column types; the standard way of doing this is to prefix (or append) + /// the column type name to the ID. Thus, you might identify methods in Java by the + /// full ID "methods_com.method.package.DeclaringClass.method(argumentList)". + + fn fresh_id(&mut self) -> Label { + let label = Label(self.counter); + self.counter += 1; + self.trap_output.push(TrapEntry::FreshId(label)); + label + } + + fn global_id(&mut self, key: &str) -> (Label, bool) { + if let Some(label) = self.global_keys.get(key) { + return (*label, false); + } + let label = Label(self.counter); + self.counter += 1; + self.global_keys.insert(key.to_owned(), label); + self.trap_output + .push(TrapEntry::MapLabelToKey(label, key.to_owned())); + (label, true) + } + + fn add_tuple(&mut self, table_name: &str, args: Vec) { + self.trap_output + .push(TrapEntry::GenericTuple(table_name.to_owned(), args)) + } + + fn populate_file(&mut self, absolute_path: &Path) -> Label { + let (file_label, fresh) = self.global_id(&full_id_for_file(absolute_path)); + if fresh { + self.add_tuple( + "files", + vec![ + Arg::Label(file_label), + Arg::String(normalize_path(absolute_path)), + ], + ); + self.populate_parent_folders(file_label, absolute_path.parent()); + } + 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; + loop { + match path { + None => break, + Some(folder) => { + let (folder_label, fresh) = self.global_id(&full_id_for_folder(folder)); + self.add_tuple( + "containerparent", + vec![Arg::Label(folder_label), Arg::Label(child_label)], + ); + if fresh { + self.add_tuple( + "folders", + vec![ + Arg::Label(folder_label), + Arg::String(normalize_path(folder)), + ], + ); + path = folder.parent(); + child_label = folder_label; + } else { + break; + } + } + } + } + } + + fn location( + &mut self, + file_label: Label, + start_line: usize, + start_column: usize, + end_line: usize, + end_column: usize, + ) -> Label { + let (loc_label, fresh) = self.global_id(&format!( + "loc,{{{}}},{},{},{},{}", + file_label, start_line, start_column, end_line, end_column + )); + if fresh { + self.add_tuple( + "locations_default", + vec![ + Arg::Label(loc_label), + Arg::Label(file_label), + Arg::Int(start_line), + Arg::Int(start_column), + Arg::Int(end_line), + Arg::Int(end_column), + ], + ); + } + loc_label + } + + 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: &[u8], + ranges: &[Range], +) -> std::io::Result<()> { + let span = span!( + Level::TRACE, + "extract", + file = %path.display() + ); + + let _enter = span.enter(); + + info!("extracting: {}", path.display()); + + let mut parser = Parser::new(); + parser.set_language(language).unwrap(); + parser.set_included_ranges(ranges).unwrap(); + let tree = parser.parse(&source, None).expect("Failed to parse file"); + trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display())); + let file_label = &trap_writer.populate_file(path); + let mut visitor = Visitor { + source, + trap_writer, + // TODO: should we handle path strings that are not valid UTF8 better? + path: format!("{}", path.display()), + file_label: *file_label, + toplevel_child_counter: 0, + stack: Vec::new(), + language_prefix, + schema, + }; + traverse(&tree, &mut visitor); + + parser.reset(); + 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 { + matches!(c, '&' | '{' | '}' | '"' | '@' | '#') + } + + let key = key.into(); + if key.contains(needs_escaping) { + let mut escaped = String::with_capacity(2 * key.len()); + for c in key.chars() { + match c { + '&' => escaped.push_str("&"), + '{' => escaped.push_str("{"), + '}' => escaped.push_str("}"), + '"' => escaped.push_str("""), + '@' => escaped.push_str("@"), + '#' => escaped.push_str("#"), + _ => escaped.push(c), + } + } + Cow::Owned(escaped) + } else { + key + } +} + +/// Normalizes the path according the common CodeQL specification. Assumes that +/// `path` has already been canonicalized using `std::fs::canonicalize`. +fn normalize_path(path: &Path) -> String { + if cfg!(windows) { + // The way Rust canonicalizes paths doesn't match the CodeQL spec, so we + // have to do a bit of work removing certain prefixes and replacing + // backslashes. + let mut components: Vec = Vec::new(); + for component in path.components() { + match component { + std::path::Component::Prefix(prefix) => match prefix.kind() { + std::path::Prefix::Disk(letter) | std::path::Prefix::VerbatimDisk(letter) => { + components.push(format!("{}:", letter as char)); + } + std::path::Prefix::Verbatim(x) | std::path::Prefix::DeviceNS(x) => { + components.push(x.to_string_lossy().to_string()); + } + std::path::Prefix::UNC(server, share) + | std::path::Prefix::VerbatimUNC(server, share) => { + components.push(server.to_string_lossy().to_string()); + components.push(share.to_string_lossy().to_string()); + } + }, + std::path::Component::Normal(n) => { + components.push(n.to_string_lossy().to_string()); + } + std::path::Component::RootDir => {} + std::path::Component::CurDir => {} + std::path::Component::ParentDir => {} + } + } + components.join("/") + } else { + // For other operating systems, we can use the canonicalized path + // without modifications. + format!("{}", path.display()) + } +} + +fn full_id_for_file(path: &Path) -> String { + format!("{};sourcefile", escape_key(&normalize_path(path))) +} + +fn full_id_for_folder(path: &Path) -> String { + format!("{};folder", escape_key(&normalize_path(path))) +} + +struct ChildNode { + field_name: Option<&'static str>, + label: Label, + type_name: TypeName, +} + +struct Visitor<'a> { + /// The file path of the source code (as string) + path: String, + /// The label to use whenever we need to refer to the `@file` entity of this + /// source file. + file_label: Label, + /// The source code as a UTF-8 byte array + source: &'a [u8], + /// A TrapWriter to accumulate trap entries + 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 + /// entered the parent's [Label], child counter, and an empty list is pushed. + /// All children append their data to the the list. When the visitor leaves a + /// node the list containing the child data is popped from the stack and + /// matched against the dbscheme for the node. If the expectations are met + /// the corresponding row definitions are added to the trap_output. + stack: Vec<(Label, usize, Vec)>, +} + +impl Visitor<'_> { + fn record_parse_error( + &mut self, + error_message: String, + full_error_message: String, + loc: Label, + ) { + error!("{}", full_error_message); + let id = self.trap_writer.fresh_id(); + self.trap_writer.add_tuple( + "diagnostics", + vec![ + Arg::Label(id), + Arg::Int(40), // severity 40 = error + Arg::String("parse_error".to_string()), + Arg::String(error_message), + Arg::String(full_error_message), + Arg::Label(loc), + ], + ); + } + + fn record_parse_error_for_node( + &mut self, + error_message: String, + full_error_message: String, + node: 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, + start_column, + end_line, + end_column, + ); + self.record_parse_error(error_message, full_error_message, loc); + } + + fn enter_node(&mut self, node: Node) -> bool { + if node.is_error() || node.is_missing() { + let error_message = if node.is_missing() { + format!("parse error: expecting '{}'", node.kind()) + } else { + "parse error".to_string() + }; + 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); + return false; + } + + let id = self.trap_writer.fresh_id(); + + self.stack.push((id, 0, Vec::new())); + true + } + + fn leave_node(&mut self, field_name: Option<&'static str>, node: Node) { + if node.is_error() || node.is_missing() { + 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 loc = self.trap_writer.location( + self.file_label, + start_line, + start_column, + end_line, + end_column, + ); + let table = self + .schema + .get(&TypeName { + kind: node.kind().to_owned(), + named: node.is_named(), + }) + .unwrap(); + let mut valid = true; + let (parent_id, parent_index) = match self.stack.last_mut() { + Some(p) if !node.is_extra() => { + p.1 += 1; + (p.0, p.1 - 1) + } + _ => { + self.toplevel_child_counter += 1; + (self.file_label, self.toplevel_child_counter - 1) + } + }; + match &table.kind { + EntryKind::Token { kind_id, .. } => { + self.trap_writer.add_tuple( + &format!("{}_ast_node_parent", self.language_prefix), + vec![ + Arg::Label(id), + Arg::Label(parent_id), + Arg::Int(parent_index), + ], + ); + self.trap_writer.add_tuple( + &format!("{}_tokeninfo", self.language_prefix), + vec![ + Arg::Label(id), + Arg::Int(*kind_id), + sliced_source_arg(self.source, node), + Arg::Label(loc), + ], + ); + } + EntryKind::Table { + fields, + name: table_name, + } => { + if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) { + self.trap_writer.add_tuple( + &format!("{}_ast_node_parent", self.language_prefix), + vec![ + Arg::Label(id), + Arg::Label(parent_id), + Arg::Int(parent_index), + ], + ); + 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); + } + } + _ => { + let error_message = format!("unknown table type: '{}'", node.kind()); + let full_error_message = format!( + "{}:{}: {}", + &self.path, + node.start_position().row + 1, + error_message + ); + self.record_parse_error(error_message, full_error_message, loc); + + valid = false; + } + } + if valid && !node.is_extra() { + // Extra nodes are independent root nodes and do not belong to the parent node + // Therefore we should not register them in the parent vector + if let Some(parent) = self.stack.last_mut() { + parent.2.push(ChildNode { + field_name, + label: id, + type_name: TypeName { + kind: node.kind().to_owned(), + named: node.is_named(), + }, + }); + }; + } + } + + fn complex_node( + &mut self, + node: &Node, + fields: &[Field], + child_nodes: &[ChildNode], + parent_id: Label, + ) -> Option> { + let mut map: Map<&Option, (&Field, Vec)> = Map::new(); + for field in fields { + map.insert(&field.name, (field, Vec::new())); + } + for child_node in child_nodes { + if let Some((field, values)) = map.get_mut(&child_node.field_name.map(|x| x.to_owned())) + { + //TODO: handle error and missing nodes + if self.type_matches(&child_node.type_name, &field.type_info) { + if let node_types::FieldTypeInfo::ReservedWordInt(int_mapping) = + &field.type_info + { + // We can safely unwrap because type_matches checks the key is in the map. + let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap(); + values.push(Arg::Int(*int_value)); + } else { + values.push(Arg::Label(child_node.label)); + } + } else if field.name.is_some() { + let error_message = format!( + "type mismatch for field {}::{} with type {:?} != {:?}", + node.kind(), + child_node.field_name.unwrap_or("child"), + child_node.type_name, + field.type_info + ); + 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(); + let mut is_valid = true; + for field in fields { + let child_values = &map.get(&field.name).unwrap().1; + match &field.storage { + Storage::Column { name: column_name } => { + if child_values.len() == 1 { + args.push(child_values.first().unwrap().clone()); + } else { + is_valid = false; + let error_message = format!( + "{} for field: {}::{}", + if child_values.is_empty() { + "missing value" + } else { + "too many values" + }, + node.kind(), + column_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); + } + } + Storage::Table { + name: table_name, + has_index, + column_name: _, + } => { + for (index, child_value) in child_values.iter().enumerate() { + if !*has_index && index > 0 { + error!( + "{}:{}: too many values for field: {}::{}", + &self.path, + node.start_position().row + 1, + node.kind(), + table_name, + ); + break; + } + 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); + } + } + } + } + if is_valid { + Some(args) + } else { + None + } + } + + fn type_matches(&self, tp: &TypeName, type_info: &node_types::FieldTypeInfo) -> bool { + match type_info { + node_types::FieldTypeInfo::Single(single_type) => { + if tp == single_type { + 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, .. } => { + return self.type_matches_set(tp, types); + } + + node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => { + return !tp.named && int_mapping.contains_key(&tp.kind) + } + } + false + } + + fn type_matches_set(&self, tp: &TypeName, types: &Set) -> bool { + if types.contains(tp) { + return true; + } + for other in types.iter() { + if let EntryKind::Union { members } = &self.schema.get(other).unwrap().kind { + if self.type_matches_set(tp, members) { + return true; + } + } + } + false + } +} + +// Emit a slice of a source file as an 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()) +} + +// 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(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. + // This means that all values should be incremented by 1 and in addition the + // end position needs to be shift 1 to the left. In most cases this means + // simply incrementing all values except the end column except in cases where + // the end column is 0 (start of a line). In such cases the end position must be + // set to the end of the previous line. + let start_line = n.start_position().row + 1; + let start_col = n.start_position().column + 1; + let mut end_line = n.end_position().row + 1; + let mut end_col = n.end_position().column; + if start_line > end_line || start_line == end_line && start_col > end_col { + // the range is empty, clip it to sensible values + end_line = start_line; + end_col = start_col - 1; + } else if end_col == 0 { + // end_col = 0 means that we are at the start of a line + // unfortunately 0 is invalid as column number, therefore + // we should update the end location to be the end of the + // previous line + let mut index = n.end_byte(); + if index > 0 && index <= source.len() { + index -= 1; + if source[index] != b'\n' { + error!("expecting a line break symbol, but none found while correcting end column value"); + } + end_line -= 1; + end_col = 1; + while index > 0 && source[index - 1] != b'\n' { + index -= 1; + end_col += 1; + } + } else { + error!( + "cannot correct end column value: end_byte index {} is not in range [1,{}]", + index, + source.len() + ); + } + } + (start_line, start_col, end_line, end_col) +} + +fn traverse(tree: &Tree, visitor: &mut Visitor) { + let cursor = &mut tree.walk(); + visitor.enter_node(cursor.node()); + let mut recurse = true; + loop { + if recurse && cursor.goto_first_child() { + recurse = visitor.enter_node(cursor.node()); + } else { + visitor.leave_node(cursor.field_name(), cursor.node()); + + if cursor.goto_next_sibling() { + recurse = visitor.enter_node(cursor.node()); + } else if cursor.goto_parent() { + recurse = false; + } else { + break; + } + } + } +} + +pub struct Program(Vec); + +impl fmt::Display for Program { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut text = String::new(); + for trap_entry in &self.0 { + text.push_str(&format!("{}\n", trap_entry)); + } + write!(f, "{}", text) + } +} + +enum TrapEntry { + /// Maps the label to a fresh id, e.g. `#123=*`. + FreshId(Label), + /// Maps the label to a key, e.g. `#7=@"foo"`. + MapLabelToKey(Label, String), + /// foo_bar(arg*) + GenericTuple(String, Vec), + Comment(String), +} +impl fmt::Display for TrapEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TrapEntry::FreshId(label) => write!(f, "{}=*", label), + TrapEntry::MapLabelToKey(label, key) => { + 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, "{}", arg)?; + } + write!(f, ")") + } + TrapEntry::Comment(line) => write!(f, "// {}", line), + } + } +} + +#[derive(Debug, Copy, Clone)] +// Identifiers of the form #0, #1... +struct Label(u32); + +impl fmt::Display for Label { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "#{:x}", self.0) + } +} + +// Numeric indices. +#[derive(Debug, Copy, Clone)] +struct Index(usize); + +impl fmt::Display for Index { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +// Some untyped argument to a TrapEntry. +#[derive(Debug, Clone)] +enum Arg { + Label(Label), + Int(usize), + String(String), +} + +const MAX_STRLEN: usize = 1048576; + +impl fmt::Display for Arg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Arg::Label(x) => write!(f, "{}", x), + Arg::Int(x) => write!(f, "{}", x), + Arg::String(x) => write!( + f, + "\"{}\"", + limit_string(x, MAX_STRLEN).replace("\"", "\"\"") + ), + } + } +} + +/// Limit the length (in bytes) of a string. If the string's length in bytes is +/// less than or equal to the limit then the entire string is returned. Otherwise +/// 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: &str, max_size: usize) -> &str { + if string.len() <= max_size { + return string; + } + 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 && (p[index] & 0b11000000) == 0b10000000 { + index -= 1; + } + &string[0..index] +} + +#[test] +fn limit_string_test() { + assert_eq!("hello", limit_string(&"hello world".to_owned(), 5)); + assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6)); + assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5)); +} + +#[test] +fn escape_key_test() { + assert_eq!("foo!", escape_key("foo!")); + assert_eq!("foo{}", escape_key("foo{}")); + assert_eq!("{}", escape_key("{}")); + assert_eq!("", escape_key("")); + assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb")); + assert_eq!( + "/path/to/foo&{}"@#.rb", + escape_key("/path/to/foo&{}\"@#.rb") + ); +} diff --git a/ql/extractor/src/main.rs b/ql/extractor/src/main.rs new file mode 100644 index 000000000000..0fcf96077025 --- /dev/null +++ b/ql/extractor/src/main.rs @@ -0,0 +1,221 @@ +mod extractor; + +extern crate num_cpus; + +use flate2::write::GzEncoder; +use rayon::prelude::*; +use std::fs; +use std::io::{BufRead, BufWriter}; +use std::path::{Path, PathBuf}; + +enum TrapCompression { + None, + Gzip, +} + +impl TrapCompression { + fn from_env() -> TrapCompression { + match std::env::var("CODEQL_QL_TRAP_COMPRESSION") { + Ok(method) => match TrapCompression::from_string(&method) { + Some(c) => c, + None => { + tracing::error!("Unknown compression method '{}'; using gzip.", &method); + TrapCompression::Gzip + } + }, + // Default compression method if the env var isn't set: + Err(_) => TrapCompression::Gzip, + } + } + + fn from_string(s: &str) -> Option { + match s.to_lowercase().as_ref() { + "none" => Some(TrapCompression::None), + "gzip" => Some(TrapCompression::Gzip), + _ => None, + } + } + + fn extension(&self) -> &str { + match self { + TrapCompression::None => "trap", + TrapCompression::Gzip => "trap.gz", + } + } +} + +/** + * Gets the number of threads the extractor should use, by reading the + * CODEQL_THREADS environment variable and using it as described in the + * extractor spec: + * + * "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." + */ +fn num_codeql_threads() -> usize { + 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, + + Err(_) => { + tracing::error!( + "Unable to parse CODEQL_THREADS value '{}'; defaulting to 1 thread.", + &threads_str + ); + 1 + } + } +} + +fn main() -> std::io::Result<()> { + tracing_subscriber::fmt() + .with_target(false) + .without_time() + .with_level(true) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + let num_threads = num_codeql_threads(); + tracing::info!( + "Using {} {}", + num_threads, + if num_threads == 1 { + "thread" + } else { + "threads" + } + ); + rayon::ThreadPoolBuilder::new() + .num_threads(num_threads) + .build_global() + .unwrap(); + + let matches = clap::App::new("QL extractor") + .version("1.0") + .author("GitHub") + .about("CodeQL QL extractor") + .args_from_usage( + "--source-archive-dir= 'Sets a custom source archive folder' + --output-dir= 'Sets a custom trap folder' + --file-list= 'A text files containing the paths of the files to extract'", + ) + .get_matches(); + let src_archive_dir = matches + .value_of("source-archive-dir") + .expect("missing --source-archive-dir"); + let src_archive_dir = PathBuf::from(src_archive_dir); + + let trap_dir = matches + .value_of("output-dir") + .expect("missing --output-dir"); + let trap_dir = PathBuf::from(trap_dir); + let trap_compression = TrapCompression::from_env(); + + let file_list = matches.value_of("file-list").expect("missing --file-list"); + let file_list = fs::File::open(file_list)?; + + let language = tree_sitter_ql::language(); + 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 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 { + let mut result = PathBuf::from(dir); + for component in path.components() { + match component { + std::path::Component::Prefix(prefix) => match prefix.kind() { + std::path::Prefix::Disk(letter) | std::path::Prefix::VerbatimDisk(letter) => { + result.push(format!("{}_", letter as char)) + } + std::path::Prefix::Verbatim(x) | std::path::Prefix::DeviceNS(x) => { + result.push(x); + } + std::path::Prefix::UNC(server, share) + | std::path::Prefix::VerbatimUNC(server, share) => { + result.push("unc"); + result.push(server); + result.push(share); + } + }, + std::path::Component::RootDir => { + // skip + } + std::path::Component::Normal(_) => { + result.push(component); + } + std::path::Component::CurDir => { + // skip + } + std::path::Component::ParentDir => { + result.pop(); + } + } + } + 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/ql/generator/Cargo.toml b/ql/generator/Cargo.toml new file mode 100644 index 000000000000..38074ab4e912 --- /dev/null +++ b/ql/generator/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ql-generator" +version = "0.1.0" +authors = ["GitHub"] +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"] } +tree-sitter-ql = { git = "https://github.com/tausbn/tree-sitter-ql.git", rev = "36bdc0eae196f9833182ce3f8932be63534121b3" } diff --git a/ql/generator/src/dbscheme.rs b/ql/generator/src/dbscheme.rs new file mode 100644 index 000000000000..335eee1950c2 --- /dev/null +++ b/ql/generator/src/dbscheme.rs @@ -0,0 +1,130 @@ +use crate::ql; +use std::collections::BTreeSet as Set; +use std::fmt; +/// Represents a distinct entry in the database schema. +pub enum Entry<'a> { + /// An entry defining a database table. + Table(Table<'a>), + /// An entry defining a database table. + Case(Case<'a>), + /// An entry defining type that is a union of other types. + Union(Union<'a>), +} + +/// A table in the database schema. +pub struct Table<'a> { + pub name: &'a str, + pub columns: Vec>, + pub keysets: Option>, +} + +/// A union in the database schema. +pub struct Union<'a> { + pub name: &'a str, + pub members: Set<&'a str>, +} + +/// A table in the database schema. +pub struct Case<'a> { + pub name: &'a str, + pub column: &'a str, + pub branches: Vec<(usize, &'a str)>, +} + +/// A column in a table. +pub struct Column<'a> { + pub db_type: DbColumnType, + pub name: &'a str, + pub unique: bool, + pub ql_type: ql::Type<'a>, + pub ql_type_is_ref: bool, +} + +/// The database column type. +pub enum DbColumnType { + Int, + String, +} + +impl<'a> fmt::Display for Case<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "case @{}.{} of", &self.name, &self.column)?; + let mut sep = " "; + for (c, tp) in &self.branches { + writeln!(f, "{} {} = @{}", sep, c, tp)?; + sep = "|"; + } + writeln!(f, ";") + } +} + +impl<'a> fmt::Display for Table<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(keyset) = &self.keysets { + write!(f, "#keyset[")?; + for (key_index, key) in keyset.iter().enumerate() { + if key_index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", key)?; + } + writeln!(f, "]")?; + } + + writeln!(f, "{}(", self.name)?; + for (column_index, column) in self.columns.iter().enumerate() { + write!(f, " ")?; + if column.unique { + write!(f, "unique ")?; + } + write!( + f, + "{} ", + match column.db_type { + DbColumnType::Int => "int", + DbColumnType::String => "string", + } + )?; + write!(f, "{}: {}", column.name, column.ql_type)?; + if column.ql_type_is_ref { + write!(f, " ref")?; + } + if column_index + 1 != self.columns.len() { + write!(f, ",")?; + } + writeln!(f)?; + } + write!(f, ");")?; + + Ok(()) + } +} + +impl<'a> fmt::Display for Union<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "@{} = ", self.name)?; + let mut first = true; + for member in &self.members { + if first { + first = false; + } else { + write!(f, " | ")?; + } + write!(f, "@{}", member)?; + } + Ok(()) + } +} + +/// Generates the dbscheme by writing the given dbscheme `entries` to the `file`. +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)?, + Entry::Table(table) => write!(file, "{}\n\n", table)?, + Entry::Union(union) => write!(file, "{}\n\n", union)?, + } + } + + Ok(()) +} diff --git a/ql/generator/src/language.rs b/ql/generator/src/language.rs new file mode 100644 index 000000000000..f0b0ed1790f2 --- /dev/null +++ b/ql/generator/src/language.rs @@ -0,0 +1,4 @@ +pub struct Language { + pub name: String, + pub node_types: &'static str, +} diff --git a/ql/generator/src/main.rs b/ql/generator/src/main.rs new file mode 100644 index 000000000000..04e7cd61d2cf --- /dev/null +++ b/ql/generator/src/main.rs @@ -0,0 +1,665 @@ +mod dbscheme; +mod language; +mod ql; +mod ql_gen; + +use language::Language; +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; + +/// 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 +/// entry that should be added. +fn make_field_type<'a>( + parent_name: &'a str, + field: &'a node_types::Field, + nodes: &'a node_types::NodeTypeMap, +) -> (ql::Type<'a>, Option>) { + match &field.type_info { + node_types::FieldTypeInfo::Multiple { + types, + dbscheme_union, + ql_class: _, + } => { + // This field can have one of several types. Create an ad-hoc QL union + // type to represent them. + let members: Set<&str> = types + .iter() + .map(|t| nodes.get(t).unwrap().dbscheme_name.as_str()) + .collect(); + ( + ql::Type::At(dbscheme_union), + Some(dbscheme::Entry::Union(dbscheme::Union { + name: dbscheme_union, + members, + })), + ) + } + node_types::FieldTypeInfo::Single(t) => { + 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.values() { + branches.push((*value, name)); + } + let case = dbscheme::Entry::Case(dbscheme::Case { + name: parent_name, + column: match &field.storage { + node_types::Storage::Column { name } => name, + node_types::Storage::Table { name, .. } => name, + }, + branches, + }); + (ql::Type::Int, Some(case)) + } + } +} + +fn add_field_for_table_storage<'a>( + field: &'a node_types::Field, + table_name: &'a str, + column_name: &'a str, + has_index: bool, + nodes: &'a node_types::NodeTypeMap, +) -> (dbscheme::Table<'a>, Option>) { + 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 parent_column = dbscheme::Column { + unique: !has_index, + db_type: dbscheme::DbColumnType::Int, + name: parent_name, + ql_type: ql::Type::At(parent_name), + ql_type_is_ref: true, + }; + let index_column = dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "index", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }; + let field_column = dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: column_name, + ql_type: field_ql_type, + ql_type_is_ref: true, + }; + let field_table = dbscheme::Table { + name: table_name, + columns: if has_index { + vec![parent_column, index_column, field_column] + } else { + vec![parent_column, field_column] + }, + // 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"]) + } else { + None + }, + }; + (field_table, field_type_entry) +} + +fn add_field_for_column_storage<'a>( + parent_name: &'a str, + field: &'a node_types::Field, + column_name: &'a str, + nodes: &'a node_types::NodeTypeMap, +) -> (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); + ( + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: column_name, + ql_type: field_ql_type, + ql_type_is_ref: true, + }, + field_type_entry, + ) +} + +/// Converts the given tree-sitter node types into CodeQL dbscheme entries. +/// 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() + .filter_map(|(_, node)| match &node.kind { + node_types::EntryKind::Token { kind_id } => { + Some((node.dbscheme_name.as_str(), *kind_id)) + } + _ => None, + }) + .collect(); + 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 + // type. + let members: Set<&str> = n_members + .iter() + .map(|n| nodes.get(n).unwrap().dbscheme_name.as_str()) + .collect(); + entries.push(dbscheme::Entry::Union(dbscheme::Union { + name: &node.dbscheme_name, + members, + })); + } + node_types::EntryKind::Table { name, fields } => { + // It's a product type, defined by a table. + let mut main_table = dbscheme::Table { + name, + columns: vec![dbscheme::Column { + db_type: dbscheme::DbColumnType::Int, + name: "id", + unique: true, + ql_type: ql::Type::At(&node.dbscheme_name), + ql_type_is_ref: false, + }], + keysets: None, + }; + ast_node_members.insert(&node.dbscheme_name); + + // If the type also has fields or children, then we create either + // auxiliary tables or columns in the defining table for them. + for field in fields { + match &field.storage { + node_types::Storage::Column { name: column_name } => { + let (field_column, field_type_entry) = add_field_for_column_storage( + &node.dbscheme_name, + field, + column_name, + nodes, + ); + if let Some(field_type_entry) = field_type_entry { + entries.push(field_type_entry); + } + main_table.columns.push(field_column); + } + node_types::Storage::Table { + name, + has_index, + column_name, + } => { + let (field_table, field_type_entry) = add_field_for_table_storage( + field, + name, + column_name, + *has_index, + nodes, + ); + if let Some(field_type_entry) = field_type_entry { + entries.push(field_type_entry); + } + entries.push(dbscheme::Entry::Table(field_table)); + } + } + } + + if fields.is_empty() { + // There were no fields and no children, so it's a leaf node in + // the TS grammar. Add a column for the node text. + main_table.columns.push(dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "text", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }); + } + + // Finally, the type's defining table also includes the location. + main_table.columns.push(dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "loc", + ql_type: ql::Type::At("location"), + ql_type_is_ref: true, + }); + + entries.push(dbscheme::Entry::Table(main_table)); + } + node_types::EntryKind::Token { .. } => {} + } + } + + (entries, ast_node_members, token_kinds) +} + +/// 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, + columns: vec![ + dbscheme::Column { + db_type: dbscheme::DbColumnType::Int, + name: "child", + unique: false, + 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::At(name), + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "parent_index", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + ], + keysets: Some(vec!["parent", "parent_index"]), + } +} + +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::At(type_name), + ql_type_is_ref: false, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "kind", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "value", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "loc", + 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(); + dbscheme::Case { + name, + column: "kind", + branches, + } +} + +fn create_location_union<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Union(dbscheme::Union { + name: "location", + members: vec!["location_default"].into_iter().collect(), + }) +} + +fn create_files_table<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Table(dbscheme::Table { + name: "files", + keysets: None, + columns: vec![ + dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: "id", + ql_type: ql::Type::At("file"), + ql_type_is_ref: false, + }, + dbscheme::Column { + db_type: dbscheme::DbColumnType::String, + name: "name", + unique: false, + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + ], + }) +} +fn create_folders_table<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Table(dbscheme::Table { + name: "folders", + keysets: None, + columns: vec![ + dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: "id", + ql_type: ql::Type::At("folder"), + ql_type_is_ref: false, + }, + dbscheme::Column { + db_type: dbscheme::DbColumnType::String, + name: "name", + unique: false, + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + ], + }) +} + +fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Table(dbscheme::Table { + name: "locations_default", + keysets: None, + columns: vec![ + dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: "id", + 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::At("file"), + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "start_line", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "start_column", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "end_line", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "end_column", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + ], + }) +} + +fn create_container_union<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Union(dbscheme::Union { + name: "container", + members: vec!["folder", "file"].into_iter().collect(), + }) +} + +fn create_containerparent_table<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Table(dbscheme::Table { + name: "containerparent", + columns: vec![ + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "parent", + 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::At("container"), + ql_type_is_ref: true, + }, + ], + keysets: None, + }) +} + +fn create_source_location_prefix_table<'a>() -> dbscheme::Entry<'a> { + dbscheme::Entry::Table(dbscheme::Table { + name: "sourceLocationPrefix", + keysets: None, + columns: vec![dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "prefix", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }], + }) +} + +fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) { + let table = dbscheme::Table { + name: "diagnostics", + keysets: None, + columns: vec![ + dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: "id", + ql_type: ql::Type::At("diagnostic"), + ql_type_is_ref: false, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "severity", + ql_type: ql::Type::Int, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "error_tag", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "error_message", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::String, + name: "full_error_message", + ql_type: ql::Type::String, + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "location", + ql_type: ql::Type::At("location_default"), + ql_type_is_ref: true, + }, + ], + }; + let severities: Vec<(usize, &str)> = vec![ + (10, "diagnostic_debug"), + (20, "diagnostic_info"), + (30, "diagnostic_warning"), + (40, "diagnostic_error"), + ]; + let case = dbscheme::Case { + name: "diagnostic", + column: "severity", + branches: severities, + }; + (case, table) +} + +fn main() -> std::io::Result<()> { + tracing_subscriber::fmt() + .with_target(false) + .without_time() + .with_level(true) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + 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, + }]; + 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), + ], + )?; + + 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"), + ], + )?; + + 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, + )), + ], + )?; + + 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/ql/generator/src/ql.rs b/ql/generator/src/ql.rs new file mode 100644 index 000000000000..c51903529eb0 --- /dev/null +++ b/ql/generator/src/ql.rs @@ -0,0 +1,275 @@ +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> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TopLevel::Import(x) => write!(f, "private import {}", x), + TopLevel::Class(cls) => write!(f, "{}", cls), + TopLevel::Module(m) => write!(f, "{}", m), + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Class<'a> { + pub qldoc: Option, + pub name: &'a str, + pub is_abstract: bool, + pub supertypes: BTreeSet>, + pub characteristic_predicate: Option>, + pub predicates: Vec>, +} + +impl<'a> fmt::Display for Class<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(qldoc) = &self.qldoc { + write!(f, "/** {} */", qldoc)?; + } + if self.is_abstract { + write!(f, "abstract ")?; + } + write!(f, "class {} extends ", &self.name)?; + for (index, supertype) in self.supertypes.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", supertype)?; + } + writeln!(f, " {{ ")?; + + if let Some(charpred) = &self.characteristic_predicate { + writeln!( + f, + " {}", + Predicate { + qldoc: None, + name: self.name, + overridden: false, + return_type: None, + formal_parameters: vec![], + body: charpred.clone(), + } + )?; + } + + for predicate in &self.predicates { + writeln!(f, " {}", predicate)?; + } + + write!(f, "}}")?; + + Ok(()) + } +} + +#[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> { + /// Primitive `int` type. + Int, + + /// Primitive `string` type. + String, + + /// A database type that will need to be referred to with an `@` prefix. + At(&'a str), + + /// A user-defined type. + Normal(&'a str), +} + +impl<'a> fmt::Display for Type<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Type::Int => write!(f, "int"), + Type::String => write!(f, "string"), + Type::Normal(name) => write!(f, "{}", name), + Type::At(name) => write!(f, "@{}", name), + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub enum Expression<'a> { + Var(&'a str), + String(&'a str), + Integer(usize), + Pred(&'a str, Vec>), + And(Vec>), + Or(Vec>), + Equals(Box>, Box>), + Dot(Box>, &'a str, Vec>), + Aggregate { + name: &'a str, + vars: Vec>, + range: Option>>, + expr: Box>, + second_expr: Option>>, + }, +} + +impl<'a> fmt::Display for Expression<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Expression::Var(x) => write!(f, "{}", x), + Expression::String(s) => write!(f, "\"{}\"", s), + Expression::Integer(n) => write!(f, "{}", n), + Expression::Pred(n, args) => { + write!(f, "{}(", n)?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")") + } + Expression::And(conjuncts) => { + if conjuncts.is_empty() { + write!(f, "any()") + } else { + for (index, conjunct) in conjuncts.iter().enumerate() { + if index > 0 { + write!(f, " and ")?; + } + write!(f, "({})", conjunct)?; + } + Ok(()) + } + } + Expression::Or(disjuncts) => { + if disjuncts.is_empty() { + write!(f, "none()") + } else { + for (index, disjunct) in disjuncts.iter().enumerate() { + if index > 0 { + write!(f, " or ")?; + } + write!(f, "({})", disjunct)?; + } + Ok(()) + } + } + Expression::Equals(a, b) => write!(f, "{} = {}", a, b), + Expression::Dot(x, member_pred, args) => { + write!(f, "{}.{}(", x, member_pred)?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + 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, " | ")?; + } + if let Some(range) = range { + write!(f, "{} | ", range)?; + } + write!(f, "{}", expr)?; + if let Some(second_expr) = second_expr { + write!(f, ", {}", second_expr)?; + } + write!(f, ")") + } + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Predicate<'a> { + pub qldoc: Option, + pub name: &'a str, + pub overridden: bool, + pub return_type: Option>, + pub formal_parameters: Vec>, + pub body: Expression<'a>, +} + +impl<'a> fmt::Display for Predicate<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(qldoc) = &self.qldoc { + write!(f, "/** {} */", qldoc)?; + } + if self.overridden { + write!(f, "override ")?; + } + match &self.return_type { + None => write!(f, "predicate ")?, + Some(return_type) => write!(f, "{} ", return_type)?, + } + write!(f, "{}(", self.name)?; + for (index, param) in self.formal_parameters.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", param)?; + } + write!(f, ") {{ {} }}", self.body)?; + + Ok(()) + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct FormalParameter<'a> { + pub name: &'a str, + pub param_type: Type<'a>, +} + +impl<'a> fmt::Display for FormalParameter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.param_type, self.name) + } +} + +/// 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)?; + } + Ok(()) +} diff --git a/ql/generator/src/ql_gen.rs b/ql/generator/src/ql_gen.rs new file mode 100644 index 000000000000..a9a276fd9f6c --- /dev/null +++ b/ql/generator/src/ql_gen.rs @@ -0,0 +1,610 @@ +use crate::ql; +use std::collections::BTreeSet; + +/// Creates the hard-coded `AstNode` class that acts as a supertype of all +/// classes we generate. +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( + "Gets a string representation of this element.", + )), + name: "toString", + 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::Dot( + Box::new(ql::Expression::Var("this")), + "getAPrimaryQlClass", + vec![], + )), + ), + }; + let get_location = create_none_predicate( + Some(String::from("Gets the location of this element.")), + "getLocation", + false, + Some(ql::Type::Normal("Location")), + ); + let get_a_field_or_child = create_none_predicate( + Some(String::from("Gets a field or child node of this node.")), + "getAFieldOrChild", + false, + Some(ql::Type::Normal("AstNode")), + ); + let get_parent = ql::Predicate { + qldoc: Some(String::from("Gets the parent of this element.")), + name: "getParent", + overridden: false, + return_type: Some(ql::Type::Normal("AstNode")), + formal_parameters: vec![], + body: ql::Expression::Pred( + ast_node_parent, + vec![ + ql::Expression::Var("this"), + ql::Expression::Var("result"), + ql::Expression::Var("_"), + ], + ), + }; + let get_parent_index = ql::Predicate { + qldoc: Some(String::from( + "Gets the index of this node among the children of its parent.", + )), + name: "getParentIndex", + overridden: false, + return_type: Some(ql::Type::Int), + formal_parameters: vec![], + body: ql::Expression::Pred( + ast_node_parent, + vec![ + ql::Expression::Var("this"), + ql::Expression::Var("_"), + ql::Expression::Var("result"), + ], + ), + }; + let get_a_primary_ql_class = ql::Predicate { + qldoc: Some(String::from( + "Gets the name of the primary QL class for this element.", + )), + name: "getAPrimaryQlClass", + 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::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::At(ast_node)].into_iter().collect(), + characteristic_predicate: None, + predicates: vec![ + to_string, + get_location, + get_parent, + get_parent_index, + get_a_field_or_child, + get_a_primary_ql_class, + get_primary_ql_classes, + ], + } +} + +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, 1, tokeninfo_arity), + }; + let get_location = ql::Predicate { + qldoc: Some(String::from("Gets the location of this token.")), + name: "getLocation", + overridden: true, + return_type: Some(ql::Type::Normal("Location")), + formal_parameters: vec![], + body: create_get_field_expr_for_column_storage("result", tokeninfo, 2, tokeninfo_arity), + }; + let to_string = ql::Predicate { + qldoc: Some(String::from( + "Gets a string representation of this element.", + )), + name: "toString", + overridden: true, + return_type: Some(ql::Type::String), + formal_parameters: vec![], + body: ql::Expression::Equals( + Box::new(ql::Expression::Var("result")), + 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::At(token_type), ql::Type::Normal("AstNode")] + .into_iter() + .collect(), + characteristic_predicate: None, + predicates: vec![ + get_value, + get_location, + to_string, + create_get_a_primary_ql_class("Token"), + ], + } +} + +// Creates the `ReservedWord` class. +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); + ql::Class { + qldoc: Some(String::from("A reserved word.")), + name: class_name, + is_abstract: false, + supertypes: vec![ql::Type::At(db_name), ql::Type::Normal("Token")] + .into_iter() + .collect(), + characteristic_predicate: None, + predicates: vec![get_a_primary_ql_class], + } +} + +/// Creates a predicate whose body is `none()`. +fn create_none_predicate<'a>( + qldoc: Option, + name: &'a str, + overridden: bool, + return_type: Option>, +) -> ql::Predicate<'a> { + ql::Predicate { + qldoc, + name, + overridden, + return_type, + formal_parameters: Vec::new(), + body: ql::Expression::Pred("none", vec![]), + } +} + +/// Creates an overridden `getAPrimaryQlClass` predicate that returns the given +/// name. +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.", + )), + name: "getAPrimaryQlClass", + overridden: true, + return_type: Some(ql::Type::String), + formal_parameters: vec![], + body: ql::Expression::Equals( + Box::new(ql::Expression::Var("result")), + Box::new(ql::Expression::String(class_name)), + ), + } +} + +/// Creates the `getLocation` predicate. +/// +/// # Arguments +/// +/// `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(def_table: &str, arity: usize) -> ql::Predicate { + ql::Predicate { + qldoc: Some(String::from("Gets the location of this element.")), + name: "getLocation", + overridden: true, + return_type: Some(ql::Type::Normal("Location")), + formal_parameters: vec![], + // body of the form: foo_bar_def(_, _, ..., result) + body: ql::Expression::Pred( + def_table, + [ + vec![ql::Expression::Var("this")], + vec![ql::Expression::Var("_"); arity - 2], + vec![ql::Expression::Var("result")], + ] + .concat(), + ), + } +} + +/// Creates the `getText` predicate for a leaf node. +/// +/// # Arguments +/// +/// `def_table` - the name of the table that defines the entity and its text. +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", + overridden: false, + return_type: Some(ql::Type::String), + formal_parameters: vec![], + body: ql::Expression::Pred( + def_table, + vec![ + ql::Expression::Var("this"), + ql::Expression::Var("result"), + ql::Expression::Var("_"), + ], + ), + } +} + +/// Returns an expression to get a field that's defined as a column in the parent's table. +/// +/// # Arguments +/// +/// * `result_var_name` - the name of the variable to which the resulting value should be bound +/// * `table_name` - the name of parent's defining table +/// * `column_index` - the index in that table that defines the field +/// * `arity` - the total number of columns in the table +fn create_get_field_expr_for_column_storage<'a>( + result_var_name: &'a str, + table_name: &'a str, + column_index: usize, + arity: usize, +) -> ql::Expression<'a> { + let num_underscores_before = column_index; + let num_underscores_after = arity - 2 - num_underscores_before; + ql::Expression::Pred( + table_name, + [ + vec![ql::Expression::Var("this")], + vec![ql::Expression::Var("_"); num_underscores_before], + vec![ql::Expression::Var(result_var_name)], + vec![ql::Expression::Var("_"); num_underscores_after], + ] + .concat(), + ) +} + +/// Returns an expression to get the field with the given index from its +/// auxiliary table. The index name can be "_" so the expression will hold for +/// all indices. +fn create_get_field_expr_for_table_storage<'a>( + result_var_name: &'a str, + table_name: &'a str, + index_var_name: Option<&'a str>, +) -> ql::Expression<'a> { + ql::Expression::Pred( + table_name, + match index_var_name { + Some(index_var_name) => vec![ + ql::Expression::Var("this"), + ql::Expression::Var(index_var_name), + ql::Expression::Var(result_var_name), + ], + None => vec![ql::Expression::Var("this"), ql::Expression::Var("result")], + }, + ) +} + +/// Creates a pair consisting of a predicate to get the given field, and an +/// optional expression that will get the same field. When the field can occur +/// multiple times, the predicate will take an index argument, while the +/// expression will use the "don't care" expression to hold for all occurrences. +/// +/// # Arguments +/// +/// `main_table_name` - the name of the defining table for the parent node +/// `main_table_arity` - the number of columns in the main table +/// `main_table_column_index` - a mutable reference to a column index indicating +/// where the field is in the main table. If this is used (i.e. the field has +/// column storage), then the index is incremented. +/// `parent_name` - the name of the parent node +/// `field` - the field whose getters we are creating +/// `field_type` - the db name of the field's type (possibly being a union we created) +fn create_field_getters<'a>( + main_table_name: &'a str, + main_table_arity: usize, + main_table_column_index: &mut usize, + field: &'a node_types::Field, + nodes: &'a node_types::NodeTypeMap, +) -> (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)) + } + node_types::FieldTypeInfo::Multiple { + types: _, + dbscheme_union: _, + ql_class, + } => Some(ql::Type::Normal(ql_class)), + node_types::FieldTypeInfo::ReservedWordInt(_) => Some(ql::Type::String), + }; + let formal_parameters = match &field.storage { + node_types::Storage::Column { .. } => vec![], + node_types::Storage::Table { has_index, .. } => { + if *has_index { + vec![ql::FormalParameter { + name: "i", + param_type: ql::Type::Int, + }] + } else { + vec![] + } + } + }; + + // For the expression to get a value, what variable name should the result + // be bound to? + let get_value_result_var_name = match &field.type_info { + node_types::FieldTypeInfo::ReservedWordInt(_) => "value", + node_types::FieldTypeInfo::Single(_) => "result", + node_types::FieldTypeInfo::Multiple { .. } => "result", + }; + + // Two expressions for getting the value. One that's suitable use in the + // getter predicate (where there may be a specific index), and another for + // use in `getAFieldOrChild` (where we use a "don't care" expression to + // match any index). + let (get_value, get_value_any_index) = match &field.storage { + node_types::Storage::Column { name: _ } => { + let column_index = *main_table_column_index; + *main_table_column_index += 1; + ( + create_get_field_expr_for_column_storage( + get_value_result_var_name, + main_table_name, + column_index, + main_table_arity, + ), + create_get_field_expr_for_column_storage( + get_value_result_var_name, + main_table_name, + column_index, + main_table_arity, + ), + ) + } + node_types::Storage::Table { + name: field_table_name, + has_index, + column_name: _, + } => ( + create_get_field_expr_for_table_storage( + get_value_result_var_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, + if *has_index { Some("_") } else { None }, + ), + ), + }; + let (body, optional_expr) = match &field.type_info { + node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => { + // Create an expression that binds the corresponding string to `result` for each `value`, e.g.: + // result = "foo" and value = 0 or + // result = "bar" and value = 1 or + // result = "baz" and value = 2 + let disjuncts = int_mapping + .iter() + .map(|(token_str, (value, _))| { + ql::Expression::And(vec![ + ql::Expression::Equals( + Box::new(ql::Expression::Var("result")), + Box::new(ql::Expression::String(token_str)), + ), + ql::Expression::Equals( + Box::new(ql::Expression::Var("value")), + Box::new(ql::Expression::Integer(*value)), + ), + ]) + }) + .collect(); + ( + ql::Expression::Aggregate { + name: "exists", + vars: vec![ql::FormalParameter { + name: "value", + param_type: ql::Type::Int, + }], + 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, + ) + } + node_types::FieldTypeInfo::Single(_) | node_types::FieldTypeInfo::Multiple { .. } => { + (get_value, Some(get_value_any_index)) + } + }; + let qldoc = match &field.name { + Some(name) => format!("Gets the node corresponding to the field `{}`.", name), + None => { + if formal_parameters.is_empty() { + "Gets the child of this node.".to_owned() + } else { + "Gets the `i`th child of this node.".to_owned() + } + } + }; + ( + ql::Predicate { + qldoc: Some(qldoc), + name: &field.getter_name, + overridden: false, + return_type, + formal_parameters, + body, + }, + optional_expr, + ) +} + +/// Converts the given node types into CodeQL classes wrapping the dbscheme. +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 { + if type_name.named { + token_kinds.insert(&type_name.kind); + } + } + } + + for (type_name, node) in nodes { + match &node.kind { + node_types::EntryKind::Token { kind_id: _ } => { + if type_name.named { + let get_a_primary_ql_class = create_get_a_primary_ql_class(&node.ql_class_name); + let mut supertypes: BTreeSet = BTreeSet::new(); + 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)), + name: &node.ql_class_name, + is_abstract: false, + supertypes, + characteristic_predicate: None, + predicates: vec![get_a_primary_ql_class], + })); + } + } + node_types::EntryKind::Union { members: _ } => { + // It's a tree-sitter supertype node, so we're wrapping a dbscheme + // union type. + classes.push(ql::TopLevel::Class(ql::Class { + qldoc: None, + name: &node.ql_class_name, + is_abstract: false, + supertypes: vec![ + ql::Type::At(&node.dbscheme_name), + ql::Type::Normal("AstNode"), + ] + .into_iter() + .collect(), + characteristic_predicate: None, + predicates: vec![], + })); + } + node_types::EntryKind::Table { + name: main_table_name, + fields, + } => { + // Count how many columns there will be in the main table. + // There will be: + // - one for the id + // - one for the location + // - one for each field that's stored as a column + // - if there are no fields, one for the text column. + let main_table_arity = 2 + if fields.is_empty() { + 1 + } else { + fields + .iter() + .filter(|&f| matches!(f.storage, node_types::Storage::Column { .. })) + .count() + }; + + let main_class_name = &node.ql_class_name; + let mut main_class = ql::Class { + qldoc: Some(format!("A class representing `{}` nodes.", type_name.kind)), + name: main_class_name, + is_abstract: false, + supertypes: vec![ + ql::Type::At(&node.dbscheme_name), + ql::Type::Normal("AstNode"), + ] + .into_iter() + .collect(), + characteristic_predicate: None, + predicates: vec![ + create_get_a_primary_ql_class(main_class_name), + create_get_location_predicate(main_table_name, main_table_arity), + ], + }; + + if fields.is_empty() { + main_class + .predicates + .push(create_get_text_predicate(main_table_name)); + } else { + let mut main_table_column_index: usize = 0; + let mut get_child_exprs: Vec = Vec::new(); + + // Iterate through the fields, creating: + // - classes to wrap union types if fields need them, + // - predicates to access the fields, + // - the QL expressions to access the fields that will be part of getAFieldOrChild. + for field in fields { + let (get_pred, get_child_expr) = create_field_getters( + main_table_name, + main_table_arity, + &mut main_table_column_index, + field, + nodes, + ); + main_class.predicates.push(get_pred); + if let Some(get_child_expr) = get_child_expr { + get_child_exprs.push(get_child_expr) + } + } + + main_class.predicates.push(ql::Predicate { + qldoc: Some(String::from("Gets a field or child node of this node.")), + name: "getAFieldOrChild", + overridden: true, + return_type: Some(ql::Type::Normal("AstNode")), + formal_parameters: vec![], + body: ql::Expression::Or(get_child_exprs), + }); + } + + classes.push(ql::TopLevel::Class(main_class)); + } + } + } + + classes +} diff --git a/ql/node-types/Cargo.toml b/ql/node-types/Cargo.toml new file mode 100644 index 000000000000..c751d7360d60 --- /dev/null +++ b/ql/node-types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "node-types" +version = "0.1.0" +authors = ["GitHub"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" \ No newline at end of file diff --git a/ql/node-types/src/lib.rs b/ql/node-types/src/lib.rs new file mode 100644 index 000000000000..a8c2c099da5f --- /dev/null +++ b/ql/node-types/src/lib.rs @@ -0,0 +1,440 @@ +use serde::Deserialize; +use std::collections::BTreeMap; +use std::path::Path; + +use std::collections::BTreeSet as Set; +use std::fs; + +/// A lookup table from TypeName to Entry. +pub type NodeTypeMap = BTreeMap; + +#[derive(Debug)] +pub struct Entry { + pub dbscheme_name: String, + pub ql_class_name: String, + pub kind: EntryKind, +} + +#[derive(Debug)] +pub enum EntryKind { + Union { members: Set }, + Table { name: String, fields: Vec }, + Token { kind_id: usize }, +} + +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] +pub struct TypeName { + pub kind: String, + pub named: bool, +} + +#[derive(Debug)] +pub enum FieldTypeInfo { + /// The field has a single type. + Single(TypeName), + + /// The field can take one of several types, so we also provide the name of + /// the database union type that wraps them, and the corresponding QL class + /// name. + Multiple { + types: Set, + dbscheme_union: String, + ql_class: String, + }, + + /// The field can be one of several tokens, so the db type will be an `int` + /// with a `case @foo.kind` for each possiblity. + ReservedWordInt(BTreeMap), +} + +#[derive(Debug)] +pub struct Field { + pub parent: TypeName, + pub type_info: FieldTypeInfo, + /// The name of the field or None for the anonymous 'children' + /// entry from node_types.json + pub name: Option, + /// The name of the predicate to get this field. + pub getter_name: String, + pub storage: Storage, +} + +fn name_for_field_or_child(name: &Option) -> String { + match name { + Some(name) => name.clone(), + None => "child".to_owned(), + } +} + +#[derive(Debug)] +pub enum Storage { + /// the field is stored as a column in the parent table + Column { name: String }, + /// the field is stored in a link table + Table { + /// the name of the table + name: String, + /// the name of the column for the field in the dbscheme + column_name: String, + /// does it have an associated index column? + has_index: bool, + }, +} + +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: Vec = serde_json::from_reader(file)?; + Ok(convert_nodes(prefix, &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 { + TypeName { + kind: node_type.kind.to_string(), + named: node_type.named, + } +} + +fn convert_types(node_types: &[NodeType]) -> Set { + node_types.iter().map(convert_type).collect() +} + +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() + && 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 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. + entries.insert( + TypeName { + kind: node.kind.clone(), + named: node.named, + }, + Entry { + dbscheme_name, + ql_class_name, + kind: EntryKind::Union { + members: convert_types(subtypes), + }, + }, + ); + } else if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() { + // Token kind, handled above. + } else { + // It's a product type, defined by a table. + let type_name = TypeName { + kind: node.kind.clone(), + 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 + // auxiliary tables or columns in the defining table for them. + 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, + &mut fields, + &token_kinds, + ); + } + } + if let Some(children) = &node.children { + // Treat children as if they were a field called 'child'. + add_field( + prefix, + &type_name, + None, + children, + &mut fields, + &token_kinds, + ); + } + entries.insert( + type_name, + Entry { + dbscheme_name, + ql_class_name, + kind: EntryKind::Table { + name: table_name, + fields, + }, + }, + ); + } + } + let mut counter = 0; + for type_name in token_kinds { + let entry = if type_name.named { + counter += 1; + let unprefixed_name = node_type_name(&type_name.kind, true); + Entry { + 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: format!("{}_reserved_word", &prefix), + ql_class_name: "ReservedWord".to_owned(), + kind: EntryKind::Token { kind_id: 0 }, + } + }; + entries.insert(type_name, entry); + } + entries +} + +fn add_field( + prefix: &str, + parent_type_name: &TypeName, + field_name: Option, + field_info: &FieldInfo, + fields: &mut Vec, + token_kinds: &Set, +) { + let parent_flattened_name = node_type_name(&parent_type_name.kind, parent_type_name.named); + let column_name = escape_name(&name_for_field_or_child(&field_name)); + let storage = if !field_info.multiple && field_info.required { + // This field must appear exactly once, so we add it as + // a column to the main table for the node type. + Storage::Column { name: column_name } + } else { + // 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) + )); + Storage::Table { + has_index, + name: field_table_name, + column_name, + } + }; + let converted_types = convert_types(&field_info.types); + let type_info = if field_info + .types + .iter() + .all(|t| !t.named && token_kinds.contains(&convert_type(t))) + { + // 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 field_token_ints: BTreeMap = BTreeMap::new(); + for (counter, t) in converted_types.into_iter().enumerate() { + let dbscheme_variant_name = + escape_name(&format!("{}_{}_{}", &prefix, parent_flattened_name, t.kind)); + field_token_ints.insert(t.kind.to_owned(), (counter, dbscheme_variant_name)); + } + FieldTypeInfo::ReservedWordInt(field_token_ints) + } else if field_info.types.len() == 1 { + FieldTypeInfo::Single(converted_types.into_iter().next().unwrap()) + } else { + // The dbscheme type for this field will be a union. In QL, it'll just be AstNode. + FieldTypeInfo::Multiple { + types: converted_types, + dbscheme_union: format!( + "{}_{}_{}_type", + &prefix, + &parent_flattened_name, + &name_for_field_or_child(&field_name) + ), + ql_class: "AstNode".to_owned(), + } + }; + let getter_name = format!( + "get{}", + dbscheme_name_to_class_name(&escape_name(&name_for_field_or_child(&field_name))) + ); + fields.push(Field { + parent: TypeName { + kind: parent_type_name.kind.to_string(), + named: parent_type_name.named, + }, + type_info, + name: field_name, + getter_name, + storage, + }); +} +#[derive(Deserialize)] +pub struct NodeInfo { + #[serde(rename = "type")] + pub kind: String, + pub named: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub fields: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub subtypes: Option>, +} + +#[derive(Deserialize)] +pub struct NodeType { + #[serde(rename = "type")] + pub kind: String, + pub named: bool, +} + +#[derive(Deserialize)] +pub struct FieldInfo { + pub multiple: bool, + pub required: bool, + pub types: Vec, +} + +/// Given a tree-sitter node type's (kind, named) pair, returns a single string +/// representing the (unescaped) name we'll use to refer to corresponding QL +/// type. +fn node_type_name(kind: &str, named: bool) -> String { + if named { + kind.to_string() + } else { + format!("{}_unnamed", kind) + } +} + +const RESERVED_KEYWORDS: [&str; 14] = [ + "boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype", + "type", "unique", "varchar", +]; + +/// Returns a string that's a copy of `name` but suitably escaped to be a valid +/// QL identifier. +fn escape_name(name: &str) -> String { + let mut result = String::new(); + + // If there's a leading underscore, replace it with 'underscore_'. + if let Some(c) = name.chars().next() { + if c == '_' { + result.push_str("underscore"); + } + } + for c in name.chars() { + match c { + '{' => result.push_str("lbrace"), + '}' => result.push_str("rbrace"), + '<' => result.push_str("langle"), + '>' => result.push_str("rangle"), + '[' => result.push_str("lbracket"), + ']' => result.push_str("rbracket"), + '(' => result.push_str("lparen"), + ')' => result.push_str("rparen"), + '|' => result.push_str("pipe"), + '=' => result.push_str("equal"), + '~' => result.push_str("tilde"), + '?' => result.push_str("question"), + '`' => result.push_str("backtick"), + '^' => result.push_str("caret"), + '!' => result.push_str("bang"), + '#' => result.push_str("hash"), + '%' => result.push_str("percent"), + '&' => result.push_str("ampersand"), + '.' => result.push_str("dot"), + ',' => result.push_str("comma"), + '/' => result.push_str("slash"), + ':' => result.push_str("colon"), + ';' => result.push_str("semicolon"), + '"' => result.push_str("dquote"), + '*' => result.push_str("star"), + '+' => result.push_str("plus"), + '-' => result.push_str("minus"), + '@' => result.push_str("at"), + _ if c.is_uppercase() => { + result.push('_'); + result.push_str(&c.to_lowercase().to_string()) + } + _ => result.push(c), + } + } + + for &keyword in &RESERVED_KEYWORDS { + if result == keyword { + result.push_str("__"); + break; + } + } + + 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 { + fn to_title_case(word: &str) -> String { + let mut first = true; + let mut result = String::new(); + for c in word.chars() { + if first { + first = false; + result.push(c.to_ascii_uppercase()); + } else { + result.push(c); + } + } + result + } + dbscheme_name + .split('_') + .map(|word| to_title_case(word)) + .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/ql/consistency-queries/AstConsistency.ql b/ql/ql/consistency-queries/AstConsistency.ql new file mode 100644 index 000000000000..378c9554f66d --- /dev/null +++ b/ql/ql/consistency-queries/AstConsistency.ql @@ -0,0 +1 @@ +private import codeql_ql.ast.internal.AstNodes::AstConsistency diff --git a/ql/ql/consistency-queries/BuildinsConsistency.ql b/ql/ql/consistency-queries/BuildinsConsistency.ql new file mode 100644 index 000000000000..562e123d118b --- /dev/null +++ b/ql/ql/consistency-queries/BuildinsConsistency.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Builtins::BuiltinsConsistency diff --git a/ql/ql/consistency-queries/ModuleResolution.ql b/ql/ql/consistency-queries/ModuleResolution.ql new file mode 100644 index 000000000000..49347558d8e6 --- /dev/null +++ b/ql/ql/consistency-queries/ModuleResolution.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Module::ModConsistency diff --git a/ql/ql/consistency-queries/PredicateResolution.ql b/ql/ql/consistency-queries/PredicateResolution.ql new file mode 100644 index 000000000000..b62a01d720b2 --- /dev/null +++ b/ql/ql/consistency-queries/PredicateResolution.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Predicate::PredConsistency diff --git a/ql/ql/consistency-queries/TypeResolution.ql b/ql/ql/consistency-queries/TypeResolution.ql new file mode 100644 index 000000000000..f709a28910f9 --- /dev/null +++ b/ql/ql/consistency-queries/TypeResolution.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Type::TyConsistency diff --git a/ql/ql/consistency-queries/VariableResolution.ql b/ql/ql/consistency-queries/VariableResolution.ql new file mode 100644 index 000000000000..8fc944d53637 --- /dev/null +++ b/ql/ql/consistency-queries/VariableResolution.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Variable::VarConsistency diff --git a/ql/ql/consistency-queries/qlpack.yml b/ql/ql/consistency-queries/qlpack.yml new file mode 100644 index 000000000000..55be6d913a5f --- /dev/null +++ b/ql/ql/consistency-queries/qlpack.yml @@ -0,0 +1,5 @@ +name: codeql-ql-consistency-queries +version: 0.0.0 +libraryPathDependencies: + - codeql-ql +extractor: ql diff --git a/ql/ql/docs/experimental.md b/ql/ql/docs/experimental.md new file mode 100644 index 000000000000..2a86f2093530 --- /dev/null +++ b/ql/ql/docs/experimental.md @@ -0,0 +1,37 @@ +# Experimental CodeQL queries and libraries + +In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries. + +Experimental queries and libraries may not be actively maintained as the standard libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings. + +## Requirements + +1. **Directory structure** + + - Experimental queries and libraries are stored in the `ql/src/experimental` subdirectory, and any corresponding tests in `ql/test/experimental`. + - The structure of an `experimental` subdirectory mirrors the structure of standard queries and libraries (or tests) in the parent directory. + +2. **Query metadata** + + - The query `@id` must not clash with any other queries in the repository. + - The query must have a `@name` and `@description` to explain its purpose. + - The query must have a `@kind` and `@problem.severity` as required by CodeQL tools. + + For details, see the [guide on query metadata](https://github.com/github/codeql/blob/master/docs/query-metadata-style-guide.md). + +3. **Formatting** + + - The queries and libraries must be [autoformatted](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting). + +4. **Compilation** + + - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`. + - The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations). + +5. **Results** + + - The query must have at least one true positive result on some revision of a real project. + +## Non-requirements + +Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](https://github.com/github/codeql/blob/master/docs/query-metadata-style-guide.md), query [help](https://github.com/github/codeql/blob/master/docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended). diff --git a/ql/ql/examples/qlpack.yml b/ql/ql/examples/qlpack.yml new file mode 100644 index 000000000000..c8a9c894cc56 --- /dev/null +++ b/ql/ql/examples/qlpack.yml @@ -0,0 +1,3 @@ +name: codeql-ql-examples +version: 0.0.0 +libraryPathDependencies: codeql-ql diff --git a/ql/ql/src/codeql-suites/ql-all.qls b/ql/ql/src/codeql-suites/ql-all.qls new file mode 100644 index 000000000000..e4c050bfa986 --- /dev/null +++ b/ql/ql/src/codeql-suites/ql-all.qls @@ -0,0 +1,8 @@ +- description: All Code Scanning queries for QL +- queries: . +- include: + kind: + - problem + - path-problem + - alert + - path-alert diff --git a/ql/ql/src/codeql-suites/ql-code-scanning.qls b/ql/ql/src/codeql-suites/ql-code-scanning.qls new file mode 100644 index 000000000000..f6b2a097b0a4 --- /dev/null +++ b/ql/ql/src/codeql-suites/ql-code-scanning.qls @@ -0,0 +1,18 @@ +- description: Standard Code Scanning queries for QL +- queries: . +- include: + kind: + - problem + - path-problem + - alert + - path-alert + precision: + - high + - very-high + problem.severity: + - error + - warning +- exclude: + deprecated: // +- exclude: + query path: /^experimental\/.*/ diff --git a/ql/ql/src/codeql/GlobalValueNumbering.qll b/ql/ql/src/codeql/GlobalValueNumbering.qll new file mode 100644 index 000000000000..e9b4728a6be1 --- /dev/null +++ b/ql/ql/src/codeql/GlobalValueNumbering.qll @@ -0,0 +1,244 @@ +private import ql +private import codeql_ql.ast.internal.Predicate +private import codeql_ql.ast.internal.Type +private import codeql_ql.ast.internal.Builtins + +private newtype TValueNumber = + TVariableValueNumber(VarDecl var) { variableAccessValueNumber(_, var) } or + TFieldValueNumber(VarDecl var) { fieldAccessValueNumber(_, var) } or + TThisValueNumber(Predicate pred) { thisAccessValueNumber(_, pred) } or + TPredicateValueNumber(PredicateOrBuiltin pred, ValueNumberArgumentList args) { + predicateCallValueNumber(_, pred, args) + } or + TClassPredicateValueNumber(PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args) { + classPredicateCallValueNumber(_, pred, base, args) + } or + TLiteralValueNumber(string value, Type t) { literalValueNumber(_, value, t) } or + TBinaryOpValueNumber(FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand) { + binaryOperandValueNumber(_, symbol, leftOperand, rightOperand) + } or + TUnaryOpValueNumber(FunctionSymbol symbol, ValueNumber operand) { + unaryOperandValueNumber(_, symbol, operand) + } or + TInlineCastValueNumber(ValueNumber operand, Type t) { inlineCastValueNumber(_, operand, t) } or + TDontCareValueNumber() or + TRangeValueNumber(ValueNumber lower, ValueNumber high) { rangeValueNumber(_, lower, high) } or + TSetValueNumber(ValueNumberElementList elements) { setValueNumber(_, elements) } or + TUniqueValueNumber(Expr e) { uniqueValueNumber(e) } + +private newtype ValueNumberArgumentList = + MkArgsNil() or + MkArgsCons(ValueNumber head, ValueNumberArgumentList tail) { + argumentValueNumbers(_, _, head, tail) + } + +private newtype ValueNumberElementList = + MkElementsNil() or + MkElementsCons(ValueNumber head, ValueNumberElementList tail) { + setValueNumbers(_, _, head, tail) + } + +private ValueNumberArgumentList argumentValueNumbers(Call call, int start) { + start = call.getNumberOfArguments() and + result = MkArgsNil() + or + exists(ValueNumber head, ValueNumberArgumentList tail | + argumentValueNumbers(call, start, head, tail) and + result = MkArgsCons(head, tail) + ) +} + +private predicate argumentValueNumbers( + Call call, int start, ValueNumber head, ValueNumberArgumentList tail +) { + head = valueNumber(call.getArgument(start)) and + tail = argumentValueNumbers(call, start + 1) +} + +private ValueNumberElementList setValueNumbers(Set set, int start) { + start = set.getNumberOfElements() and + result = MkElementsNil() + or + exists(ValueNumber head, ValueNumberElementList tail | + setValueNumbers(set, start, head, tail) and + result = MkElementsCons(head, tail) + ) +} + +private predicate setValueNumbers(Set set, int start, ValueNumber head, ValueNumberElementList tail) { + head = valueNumber(set.getElement(start)) and + tail = setValueNumbers(set, start + 1) +} + +/** + * A value number. A value number represents a collection of expressions that compute to the same value + * at runtime. + */ +class ValueNumber extends TValueNumber { + string toString() { result = "GVN" } + + /** Gets an expression that has this value number. */ + final Expr getAnExpr() { this = valueNumber(result) } +} + +private predicate uniqueValueNumber(Expr e) { not numberable(e) } + +private predicate numberable(Expr e) { + e instanceof VarAccess or + e instanceof FieldAccess or + e instanceof ThisAccess or + e instanceof Call or + e instanceof Literal or + e instanceof BinOpExpr or + e instanceof UnaryExpr or + e instanceof InlineCast or + e instanceof ExprAnnotation or + e instanceof DontCare or + e instanceof Range or + e instanceof Set or + e instanceof AsExpr +} + +private predicate variableAccessValueNumber(VarAccess access, VarDef var) { + access.getDeclaration() = var +} + +private predicate fieldAccessValueNumber(FieldAccess access, VarDef var) { + access.getDeclaration() = var +} + +private predicate thisAccessValueNumber(ThisAccess access, Predicate pred) { + access.getEnclosingPredicate() = pred +} + +private predicate predicateCallValueNumber( + Call call, PredicateOrBuiltin pred, ValueNumberArgumentList args +) { + call.getTarget() = pred and + not exists(call.(MemberCall).getBase()) and + args = argumentValueNumbers(call, 0) +} + +private predicate classPredicateCallValueNumber( + MemberCall call, PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args +) { + call.getTarget() = pred and + valueNumber(call.getBase()) = base and + args = argumentValueNumbers(call, 0) +} + +private predicate literalValueNumber(Literal lit, string value, Type t) { + lit.(String).getValue() = value and + t instanceof StringClass + or + lit.(Integer).getValue().toString() = value and + t instanceof IntClass + or + lit.(Float).getValue().toString() = value and + t instanceof FloatClass + or + lit.(Boolean).isFalse() and + value = "false" and + t instanceof BooleanClass + or + lit.(Boolean).isTrue() and + value = "true" and + t instanceof BooleanClass +} + +private predicate binaryOperandValueNumber( + BinOpExpr e, FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand +) { + e.getOperator() = symbol and + valueNumber(e.getLeftOperand()) = leftOperand and + valueNumber(e.getRightOperand()) = rightOperand +} + +private predicate unaryOperandValueNumber(UnaryExpr e, FunctionSymbol symbol, ValueNumber operand) { + e.getOperator() = symbol and + valueNumber(e.getOperand()) = operand +} + +private predicate inlineCastValueNumber(InlineCast cast, ValueNumber operand, Type t) { + valueNumber(cast.getBase()) = operand and + cast.getTypeExpr().getResolvedType() = t +} + +private predicate rangeValueNumber(Range range, ValueNumber lower, ValueNumber high) { + valueNumber(range.getLowEndpoint()) = lower and + valueNumber(range.getHighEndpoint()) = high +} + +private predicate setValueNumber(Set set, ValueNumberElementList elements) { + elements = setValueNumbers(set, 0) +} + +private TValueNumber nonUniqueValueNumber(Expr e) { + exists(VarDecl var | + variableAccessValueNumber(e, var) and + result = TVariableValueNumber(var) + ) + or + exists(VarDecl var | + fieldAccessValueNumber(e, var) and + result = TFieldValueNumber(var) + ) + or + exists(Predicate pred | + thisAccessValueNumber(e, pred) and + result = TThisValueNumber(pred) + ) + or + exists(PredicateOrBuiltin pred, ValueNumberArgumentList args | + predicateCallValueNumber(e, pred, args) and + result = TPredicateValueNumber(pred, args) + ) + or + exists(PredicateOrBuiltin pred, ValueNumber base, ValueNumberArgumentList args | + classPredicateCallValueNumber(e, pred, base, args) and + result = TClassPredicateValueNumber(pred, base, args) + ) + or + exists(string value, Type t | + literalValueNumber(e, value, t) and + result = TLiteralValueNumber(value, t) + ) + or + exists(FunctionSymbol symbol, ValueNumber leftOperand, ValueNumber rightOperand | + binaryOperandValueNumber(e, symbol, leftOperand, rightOperand) and + result = TBinaryOpValueNumber(symbol, leftOperand, rightOperand) + ) + or + exists(FunctionSymbol symbol, ValueNumber operand | + unaryOperandValueNumber(e, symbol, operand) and + result = TUnaryOpValueNumber(symbol, operand) + ) + or + exists(ValueNumber operand, Type t | + inlineCastValueNumber(e, operand, t) and + result = TInlineCastValueNumber(operand, t) + ) + or + result = valueNumber([e.(ExprAnnotation).getExpression(), e.(AsExpr).getInnerExpr()]) + or + e instanceof DontCare and result = TDontCareValueNumber() + or + exists(ValueNumber lower, ValueNumber high | + rangeValueNumber(e, lower, high) and + result = TRangeValueNumber(lower, high) + ) + or + exists(ValueNumberElementList elements | + setValueNumber(e, elements) and + result = TSetValueNumber(elements) + ) +} + +/** Gets the value number of an expression `e`. */ +cached +TValueNumber valueNumber(Expr e) { + result = nonUniqueValueNumber(e) + or + uniqueValueNumber(e) and + result = TUniqueValueNumber(e) +} diff --git a/ql/ql/src/codeql/IDEContextual.qll b/ql/ql/src/codeql/IDEContextual.qll new file mode 100644 index 000000000000..0e58b1d878be --- /dev/null +++ b/ql/ql/src/codeql/IDEContextual.qll @@ -0,0 +1,19 @@ +private import codeql.files.FileSystem + +/** + * Returns an appropriately encoded version of a filename `name` + * passed by the VS Code extension in order to coincide with the + * output of `.getFile()` on locatable entities. + */ +cached +File getFileBySourceArchiveName(string name) { + // The name provided for a file in the source archive by the VS Code extension + // has some differences from the absolute path in the database: + // 1. colons are replaced by underscores + // 2. there's a leading slash, even for Windows paths: "C:/foo/bar" -> + // "/C_/foo/bar" + // 3. double slashes in UNC prefixes are replaced with a single slash + // We can handle 2 and 3 together by unconditionally adding a leading slash + // before replacing double slashes. + name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/") +} diff --git a/ql/ql/src/codeql/Locations.qll b/ql/ql/src/codeql/Locations.qll new file mode 100644 index 000000000000..7497525d7af3 --- /dev/null +++ b/ql/ql/src/codeql/Locations.qll @@ -0,0 +1,53 @@ +/** Provides classes for working with locations. */ + +import files.FileSystem + +/** + * A location as given by a file, a start line, a start column, + * an end line, and an end column. + * + * For more information about locations see [LGTM locations](https://lgtm.com/help/ql/locations). + */ +class Location extends @location { + /** Gets the file for this location. */ + File getFile() { locations_default(this, result, _, _, _, _) } + + /** Gets the 1-based line number (inclusive) where this location starts. */ + int getStartLine() { locations_default(this, _, result, _, _, _) } + + /** Gets the 1-based column number (inclusive) where this location starts. */ + int getStartColumn() { locations_default(this, _, _, result, _, _) } + + /** Gets the 1-based line number (inclusive) where this location ends. */ + int getEndLine() { locations_default(this, _, _, _, result, _) } + + /** Gets the 1-based column number (inclusive) where this location ends. */ + int getEndColumn() { locations_default(this, _, _, _, _, result) } + + /** Gets the number of lines covered by this location. */ + int getNumLines() { result = getEndLine() - getStartLine() + 1 } + + /** Gets a textual representation of this element. */ + string toString() { + exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and + result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn + ) + } + + /** + * 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 + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(File f | + locations_default(this, f, startline, startcolumn, endline, endcolumn) and + filepath = f.getAbsolutePath() + ) + } +} diff --git a/ql/ql/src/codeql/files/FileSystem.qll b/ql/ql/src/codeql/files/FileSystem.qll new file mode 100644 index 000000000000..aca3c2fe0ac0 --- /dev/null +++ b/ql/ql/src/codeql/files/FileSystem.qll @@ -0,0 +1,202 @@ +/** Provides classes for working with files and folders. */ + +private import codeql_ql.ast.internal.TreeSitter +private import codeql.Locations + +/** A file or folder. */ +abstract class Container extends @container { + /** Gets a file or sub-folder in this container. */ + Container getAChildContainer() { this = result.getParentContainer() } + + /** Gets a file in this container. */ + File getAFile() { result = this.getAChildContainer() } + + /** Gets a sub-folder in this container. */ + Folder getAFolder() { result = this.getAChildContainer() } + + /** + * Gets the absolute, canonical path of this container, using forward slashes + * as path separator. + * + * The path starts with a _root prefix_ followed by zero or more _path + * segments_ separated by forward slashes. + * + * The root prefix is of one of the following forms: + * + * 1. A single forward slash `/` (Unix-style) + * 2. An upper-case drive letter followed by a colon and a forward slash, + * such as `C:/` (Windows-style) + * 3. Two forward slashes, a computer name, and then another forward slash, + * such as `//FileServer/` (UNC-style) + * + * Path segments are never empty (that is, absolute paths never contain two + * contiguous slashes, except as part of a UNC-style root prefix). Also, path + * segments never contain forward slashes, and no path segment is of the + * form `.` (one dot) or `..` (two dots). + * + * Note that an absolute path never ends with a forward slash, except if it is + * a bare root prefix, that is, the path has no path segments. A container + * whose absolute path has no segments is always a `Folder`, not a `File`. + */ + abstract string getAbsolutePath(); + + /** + * Gets the base name of this container including extension, that is, the last + * segment of its absolute path, or the empty string if it has no segments. + * + * Here are some examples of absolute paths and the corresponding base names + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + * + *
Absolute pathBase name
"/tmp/tst.go""tst.go"
"C:/Program Files (x86)""Program Files (x86)"
"/"""
"C:/"""
"D:/"""
"//FileServer/"""
+ */ + string getBaseName() { + result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) + } + + /** + * Gets the extension of this container, that is, the suffix of its base name + * after the last dot character, if any. + * + * In particular, + * + * - if the name does not include a dot, there is no extension, so this + * predicate has no result; + * - if the name ends in a dot, the extension is the empty string; + * - if the name contains multiple dots, the extension follows the last dot. + * + * Here are some examples of absolute paths and the corresponding extensions + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
Absolute pathExtension
"/tmp/tst.go""go"
"/tmp/.classpath""classpath"
"/bin/bash"not defined
"/tmp/tst2."""
"/tmp/x.tar.gz""gz"
+ */ + 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 = 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 = this.getAFolder() and + result.getBaseName() = baseName + } + + /** Gets the parent container of this file or folder, if any. */ + Container getParentContainer() { containerparent(result, this) } + + /** + * Gets the relative path of this file or folder from the root folder of the + * analyzed source location. The relative path of the root folder itself is + * the empty string. + * + * This has no result if the container is outside the source root, that is, + * if the root folder is not a reflexive, transitive parent of this container. + */ + string getRelativePath() { + exists(string absPath, string pref | + absPath = this.getAbsolutePath() and sourceLocationPrefix(pref) + | + absPath = pref and result = "" + or + absPath = pref.regexpReplaceAll("/$", "") + "/" + result and + not result.matches("/%") + ) + } + + /** + * Gets the stem of this container, that is, the prefix of its base name up to + * (but not including) the last dot character if there is one, or the entire + * base name if there is not. + * + * Here are some examples of absolute paths and the corresponding stems + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
Absolute pathStem
"/tmp/tst.go""tst"
"/tmp/.classpath"""
"/bin/bash""bash"
"/tmp/tst2.""tst2"
"/tmp/x.tar.gz""x.tar"
+ */ + string getStem() { + result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) + } + + /** + * Gets a URL representing the location of this container. + * + * For more information see https://lgtm.com/help/ql/locations#providing-urls. + */ + abstract string getURL(); + + /** + * Gets a textual representation of the path of this container. + * + * This is the absolute path of the container. + */ + string toString() { result = this.getAbsolutePath() } +} + +/** A folder. */ +class Folder extends Container, @folder { + override string getAbsolutePath() { folders(this, result) } + + /** Gets the URL of this folder. */ + override string getURL() { result = "folder://" + this.getAbsolutePath() } +} + +/** A file. */ +class File extends Container, @file { + 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 QL::Token getAToken() { result.getLocation().getFile() = this } + + /** Holds if `line` contains a token. */ + private predicate line(int line, boolean comment) { + exists(QL::Token token, Location l | + token = this.getAToken() and + l = token.getLocation() and + line in [l.getStartLine() .. l.getEndLine()] and + if token instanceof @ql_token_block_comment or token instanceof @ql_token_line_comment + then comment = true + else comment = false + ) + } + + /** Gets the number of lines in this file. */ + int getNumberOfLines() { result = max([0, this.getAToken().getLocation().getEndLine()]) } + + /** Gets the number of lines of code in this file. */ + int getNumberOfLinesOfCode() { result = count(int line | this.line(line, false)) } + + /** Gets the number of lines of comments in this file. */ + int getNumberOfLinesOfComments() { result = count(int line | this.line(line, true)) } + + /** Holds if this file was extracted from ordinary source code. */ + predicate fromSource() { any() } +} diff --git a/ql/ql/src/codeql_ql/Diagnostics.qll b/ql/ql/src/codeql_ql/Diagnostics.qll new file mode 100644 index 000000000000..b8995c01bc27 --- /dev/null +++ b/ql/ql/src/codeql_ql/Diagnostics.qll @@ -0,0 +1,52 @@ +private import codeql.Locations + +/** A diagnostic emitted during extraction, such as a parse error */ +class Diagnostic extends @diagnostic { + int severity; + string tag; + string message; + string fullMessage; + Location location; + + Diagnostic() { diagnostics(this, severity, tag, message, fullMessage, location) } + + /** + * Gets the numerical severity level associated with this diagnostic. + */ + int getSeverity() { result = severity } + + /** Gets a string representation of the severity of this diagnostic. */ + string getSeverityText() { + severity = 10 and result = "Debug" + or + severity = 20 and result = "Info" + or + severity = 30 and result = "Warning" + or + severity = 40 and result = "Error" + } + + /** Gets the error code associated with this diagnostic, e.g. parse_error. */ + string getTag() { result = tag } + + /** + * Gets the error message text associated with this diagnostic. + */ + string getMessage() { result = message } + + /** + * Gets the full error message text associated with this diagnostic. + */ + string getFullMessage() { result = fullMessage } + + /** Gets the source location of this diagnostic. */ + Location getLocation() { result = location } + + /** Gets a textual representation of this diagnostic. */ + string toString() { result = this.getMessage() } +} + +/** A diagnostic relating to a particular error in extracting a file. */ +class ExtractionError extends Diagnostic, @diagnostic_error { + ExtractionError() { this.getTag() = "parse_error" } +} diff --git a/ql/ql/src/codeql_ql/ast/Ast.qll b/ql/ql/src/codeql_ql/ast/Ast.qll new file mode 100644 index 000000000000..d4c1c465ec30 --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/Ast.qll @@ -0,0 +1,2499 @@ +import ql +private import codeql_ql.ast.internal.AstNodes +private import codeql_ql.ast.internal.Module +private import codeql_ql.ast.internal.Predicate +import codeql_ql.ast.internal.Type +private import codeql_ql.ast.internal.Variable +private import codeql_ql.ast.internal.Builtins + +bindingset[name] +private string directMember(string name) { result = name + "()" } + +bindingset[name, i] +private string indexedMember(string name, int i) { result = name + "(_)" and exists(i) } + +bindingset[name, index] +private string stringIndexedMember(string name, string index) { + result = name + "(_)" and exists(index) +} + +/** An AST node of a QL program */ +class AstNode extends TAstNode { + string toString() { result = this.getAPrimaryQlClass() } + + /** + * Gets the location of the AST node. + */ + cached + Location getLocation() { + exists(QL::AstNode node | not node instanceof QL::ParExpr | + node = toQL(this) and + result = node.getLocation() + ) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + if exists(this.getLocation()) + then this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + else ( + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + ) + } + + /** + * Gets the parent in the AST for this node. + */ + cached + AstNode getParent() { result.getAChild(_) = this } + + /** + * Gets a child of this node, which can also be retrieved using a predicate + * named `pred`. + */ + cached + AstNode getAChild(string pred) { + pred = directMember("getAnAnnotation") and result = getAnAnnotation() + or + pred = directMember("getQLDoc") and result = getQLDoc() + } + + /** + * Gets the primary QL class for the ast node. + */ + string getAPrimaryQlClass() { result = "???" } + + /** Gets the QLDoc comment for this AST node, if any. */ + QLDoc getQLDoc() { none() } + + /** Holds if `node` has an annotation with `name`. */ + predicate hasAnnotation(string name) { this.getAnAnnotation().getName() = name } + + /** Gets an annotation of this AST node. */ + Annotation getAnAnnotation() { + toQL(this).getParent() = pragma[only_bind_out](toQL(result)).getParent() + } + + /** + * Gets the predicate that contains this AST node. + */ + cached + Predicate getEnclosingPredicate() { this = getANodeInPredicate(result) } +} + +private AstNode getANodeInPredicate(Predicate pred) { + result = pred.getAChild(_) + or + result = getANodeInPredicate(pred).getAChild(_) +} + +/** A toplevel QL program, i.e. a file. */ +class TopLevel extends TTopLevel, AstNode { + QL::Ql file; + + TopLevel() { this = TTopLevel(file) } + + /** + * Gets a member from contained in this top-level module. + * Includes private members. + */ + ModuleMember getAMember() { result = this.getMember(_) } + + /** Gets the `i`'th member of this top-level module. */ + 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() } + + /** Gets a top-level class in this module. */ + Class getAClass() { result = this.getAMember() } + + /** Gets a top-level predicate in this module. */ + ClasslessPredicate getAPredicate() { result = this.getAMember() } + + /** Gets a module defined at the top-level of this module. */ + Module getAModule() { result = this.getAMember() } + + /** Gets a `newtype` defined at the top-level of this module. */ + NewType getANewType() { result = this.getAMember() } + + override ModuleMember getAChild(string pred) { + pred = directMember("getAnImport") and result = this.getAnImport() + or + pred = directMember("getAClass") and result = this.getAClass() + or + pred = directMember("getAPredicate") and result = this.getAPredicate() + or + pred = directMember("getAModule") and result = this.getAModule() + or + pred = directMember("getANewType") and result = this.getANewType() + } + + QLDoc getQLDocFor(ModuleMember m) { + exists(int i | i > 0 and result = this.getMember(i) and m = this.getMember(i + 1)) + } + + override string getAPrimaryQlClass() { result = "TopLevel" } + + override QLDoc getQLDoc() { result = this.getMember(0) } +} + +class QLDoc extends TQLDoc, AstNode { + QL::Qldoc qldoc; + + QLDoc() { this = TQLDoc(qldoc) } + + string getContents() { result = qldoc.getValue() } + + override string getAPrimaryQlClass() { result = "QLDoc" } +} + +/** + * The `from, where, select` part of a QL query. + */ +class Select extends TSelect, AstNode { + QL::Select sel; + + Select() { this = TSelect(sel) } + + /** + * Gets the `i`th variable in the `from` clause. + */ + VarDecl getVarDecl(int i) { toQL(result) = sel.getChild(i) } + + /** + * Gets the formula in the `where`. + */ + Formula getWhere() { toQL(result) = sel.getChild(_) } + + /** + * Gets the `i`th expression in the `select` clause. + */ + 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) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getWhere") and result = this.getWhere() + or + exists(int i | + pred = indexedMember("getVarDecl", i) and result = this.getVarDecl(i) + or + pred = indexedMember("getExpr", i) and result = this.getExpr(i) + or + pred = indexedMember("getOrderBy", i) and result = this.getOrderBy(i) + ) + } + + override string getAPrimaryQlClass() { result = "Select" } +} + +class PredicateOrBuiltin extends TPredOrBuiltin, AstNode { + string getName() { none() } + + Type getDeclaringType() { none() } + + Type getParameterType(int i) { none() } + + Type getReturnType() { none() } + + int getArity() { result = count(int i | exists(this.getParameterType(i))) } + + predicate isPrivate() { none() } +} + +class BuiltinPredicate extends PredicateOrBuiltin, TBuiltin { + override string toString() { result = this.getName() } + + override string getAPrimaryQlClass() { result = "BuiltinPredicate" } +} + +class BuiltinClassless extends BuiltinPredicate, TBuiltinClassless { + string name; + string ret; + string args; + + BuiltinClassless() { this = TBuiltinClassless(ret, name, args) } + + override string getName() { result = name } + + override PrimitiveType getReturnType() { result.getName() = ret } + + override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) } +} + +class BuiltinMember extends BuiltinPredicate, TBuiltinMember { + string name; + string qual; + string ret; + string args; + + BuiltinMember() { this = TBuiltinMember(qual, ret, name, args) } + + override string getName() { result = name } + + override PrimitiveType getReturnType() { result.getName() = ret } + + override PrimitiveType getParameterType(int i) { result.getName() = getArgType(args, i) } + + override PrimitiveType getDeclaringType() { result.getName() = qual } +} + +/** + * A QL predicate. + * Either a classless predicate, a class predicate, or a characteristic predicate. + */ +class Predicate extends TPredicate, AstNode, PredicateOrBuiltin, Declaration { + /** + * Gets the body of the predicate. + */ + Formula getBody() { none() } + + /** + * Gets the name of the predicate. + */ + override string getName() { none() } + + /** + * Gets the `i`th parameter of the predicate. + */ + VarDecl getParameter(int i) { none() } + + /** + * Gets the number of parameters. + */ + override int getArity() { + not this.(ClasslessPredicate).getAlias() instanceof PredicateExpr and + result = count(this.getParameter(_)) + or + exists(PredicateExpr alias | alias = this.(ClasslessPredicate).getAlias() | + result = alias.getArity() + ) + } + + /** + * Holds if this predicate is private. + */ + override predicate isPrivate() { this.hasAnnotation("private") } + + /** + * Gets the return type (if any) of the predicate. + */ + TypeExpr getReturnTypeExpr() { none() } + + override Type getReturnType() { result = this.getReturnTypeExpr().getResolvedType() } + + override Type getParameterType(int i) { result = this.getParameter(i).getType() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getBody") and result = this.getBody() + or + exists(int i | pred = indexedMember("getParameter", i) and result = this.getParameter(i)) + or + pred = directMember("getReturnTypeExpr") and result = this.getReturnTypeExpr() + } + + override string getAPrimaryQlClass() { result = "Predicate" } +} + +/** + * A relation in the database. + */ +class Relation extends TDBRelation, AstNode, Declaration { + QL::DbTable table; + + Relation() { this = TDBRelation(table) } + + /** + * Gets the name of the relation. + */ + override string getName() { result = table.getTableName().getChild().getValue() } + + private QL::DbColumn getColumn(int i) { + result = + rank[i + 1](QL::DbColumn column, int child | + table.getChild(child) = column + | + column order by child + ) + } + + /** Gets the `i`th parameter name */ + 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 = this.getColumn(i).getColType().getChild().(QL::Token).getValue() + } + + /** + * Gets the number of parameters. + */ + int getArity() { result = count(this.getColumn(_)) } + + override string getAPrimaryQlClass() { result = "Relation" } +} + +/** + * An expression that refers to a predicate, e.g. `BasicBlock::succ/2`. + */ +class PredicateExpr extends TPredicateExpr, AstNode { + QL::PredicateExpr pe; + + PredicateExpr() { this = TPredicateExpr(pe) } + + override string toString() { result = "predicate" } + + /** + * Gets the name of the predicate. + * E.g. for `BasicBlock::succ/2` the result is "succ". + */ + string getName() { + exists(QL::AritylessPredicateExpr ape, QL::LiteralId id | + ape.getParent() = pe and + id.getParent() = ape and + result = id.getValue() + ) + } + + /** + * Gets the arity of the predicate. + * E.g. for `BasicBlock::succ/2` the result is 2. + */ + int getArity() { + exists(QL::Integer i | + i.getParent() = pe and + result = i.getValue().toInt() + ) + } + + /** + * Gets the module containing the predicate. + * E.g. for `BasicBlock::succ/2` the result is a `ModuleExpr` representing "BasicBlock". + */ + ModuleExpr getQualifier() { + exists(QL::AritylessPredicateExpr ape | + ape.getParent() = pe and + toQL(result).getParent() = ape + ) + } + + /** + * Gets the predicate that this expression refers to. + */ + Predicate getResolvedPredicate() { resolvePredicateExpr(this, result) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getQualifier") and result = this.getQualifier() + } + + override string getAPrimaryQlClass() { result = "PredicateExpr" } +} + +/** + * A classless predicate. + */ +class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleDeclaration { + QL::ClasslessPredicate pred; + + ClasslessPredicate() { this = TClasslessPredicate(pred) } + + /** + * If this predicate is an alias, gets the aliased value. + * E.g. for `predicate foo = Module::bar/2;` gets `Module::bar/2`. + * The result is either a `PredicateExpr` or `HigherOrderFormula`. + */ + final AstNode getAlias() { + exists(QL::PredicateAliasBody alias | + alias.getParent() = pred and + toQL(result).getParent() = alias + ) + or + toQL(result) = pred.getChild(_).(QL::HigherOrderTerm) + } + + override string getAPrimaryQlClass() { result = "ClasslessPredicate" } + + override Formula getBody() { toQL(result) = pred.getChild(_).(QL::Body).getChild() } + + override string getName() { result = pred.getName().getValue() } + + override VarDecl getParameter(int i) { + toQL(result) = + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index) + } + + override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() } + + override AstNode getAChild(string pred_name) { + result = Predicate.super.getAChild(pred_name) + or + pred_name = directMember("getAlias") and result = this.getAlias() + or + pred_name = directMember("getBody") and result = this.getBody() + or + exists(int i | pred_name = indexedMember("getParameter", i) and result = this.getParameter(i)) + or + pred_name = directMember("getReturnTypeExpr") and result = this.getReturnTypeExpr() + } + + override predicate isPrivate() { Predicate.super.isPrivate() } +} + +/** + * A predicate in a class. + */ +class ClassPredicate extends TClassPredicate, Predicate { + QL::MemberPredicate pred; + + ClassPredicate() { this = TClassPredicate(pred) } + + override string getName() { result = pred.getName().getValue() } + + override Formula getBody() { toQL(result) = pred.getChild(_).(QL::Body).getChild() } + + override string getAPrimaryQlClass() { result = "ClassPredicate" } + + override Class getParent() { result.getAClassPredicate() = this } + + /** + * Holds if this predicate is annotated as overriding another predicate. + */ + predicate isOverride() { this.hasAnnotation("override") } + + override VarDecl getParameter(int i) { + toQL(result) = + rank[i + 1](QL::VarDecl decl, int index | decl = pred.getChild(index) | decl order by index) + } + + /** + * Gets the type representing this class. + */ + override ClassType getDeclaringType() { result.getDeclaration() = this.getParent() } + + predicate overrides(ClassPredicate other) { predOverrides(this, other) } + + override TypeExpr getReturnTypeExpr() { toQL(result) = pred.getReturnType() } + + override AstNode getAChild(string pred_name) { + result = super.getAChild(pred_name) + or + pred_name = directMember("getBody") and result = this.getBody() + or + exists(int i | pred_name = indexedMember("getParameter", i) and result = this.getParameter(i)) + or + pred_name = directMember("getReturnTypeExpr") and result = this.getReturnTypeExpr() + } +} + +/** + * A characteristic predicate of a class. + */ +class CharPred extends TCharPred, Predicate { + QL::Charpred pred; + + CharPred() { this = TCharPred(pred) } + + override string getAPrimaryQlClass() { result = "CharPred" } + + override Formula getBody() { toQL(result) = pred.getBody() } + + override string getName() { result = this.getParent().(Class).getName() } + + override AstNode getAChild(string pred_name) { + result = super.getAChild(pred_name) + or + pred_name = directMember("getBody") and result = this.getBody() + } + + override ClassType getDeclaringType() { result.getDeclaration() = this.getParent() } +} + +/** + * A variable definition. This is either a variable declaration or + * an `as` expression. + */ +class VarDef extends TVarDef, AstNode { + /** Gets the name of the declared variable. */ + string getName() { none() } + + Type getType() { none() } + + override string getAPrimaryQlClass() { result = "VarDef" } + + override string toString() { result = this.getName() } +} + +/** + * A variable declaration, with a type and a name. + */ +class VarDecl extends TVarDecl, VarDef, Declaration { + QL::VarDecl var; + + VarDecl() { this = TVarDecl(var) } + + override string getName() { result = var.getChild(1).(QL::VarName).getChild().getValue() } + + override Type getType() { result = this.getTypeExpr().getResolvedType() } + + override string getAPrimaryQlClass() { result = "VarDecl" } + + /** + * Gets the type part of this variable declaration. + */ + TypeExpr getTypeExpr() { toQL(result) = var.getChild(0) } + + /** + * Holds if this variable declaration is a private field on a class. + */ + predicate isPrivate() { + exists(QL::ClassMember member | + var = member.getChild(_).(QL::Field).getChild() and + member.getAFieldOrChild().(QL::Annotation).getName().getValue() = "private" + ) + } + + /** If this is declared in a field, returns the class type that declares it. */ + ClassType getDeclaringType() { + exists(FieldDecl f | f.getVarDecl() = this and result = f.getParent().(Class).getType()) + } + + /** + * Holds if this is a class field that overrides the field `other`. + */ + predicate overrides(VarDecl other) { fieldOverrides(this, other) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getTypeExpr") and result = this.getTypeExpr() + } + + override string toString() { result = this.getName() } +} + +/** + * A field declaration; + */ +class FieldDecl extends TFieldDecl, AstNode { + QL::Field f; + + FieldDecl() { this = TFieldDecl(f) } + + VarDecl getVarDecl() { toQL(result) = f.getChild() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getVarDecl") and result = this.getVarDecl() + } + + override string getAPrimaryQlClass() { result = "FieldDecl" } + + /** Holds if this field is annotated as overriding another field. */ + predicate isOverride() { this.hasAnnotation("override") } + + string getName() { result = getVarDecl().getName() } + + override QLDoc getQLDoc() { result = any(Class c).getQLDocFor(this) } +} + +/** + * A type reference, such as `DataFlow::Node`. + */ +class TypeExpr extends TType, AstNode { + QL::TypeExpr type; + + TypeExpr() { this = TType(type) } + + override string getAPrimaryQlClass() { result = "TypeExpr" } + + /** + * Gets the class name for the type. + * E.g. `Node` in `DataFlow::Node`. + * Also gets the name for primitive types such as `string` or `int` + * or db-types such as `@locateable`. + */ + string getClassName() { + result = type.getName().getValue() + or + result = type.getChild().(QL::PrimitiveType).getValue() + or + result = type.getChild().(QL::Dbtype).getValue() + } + + /** + * Holds if this type is a primitive such as `string` or `int`. + */ + predicate isPrimitive() { type.getChild() instanceof QL::PrimitiveType } + + /** + * Holds if this type is a db-type. + */ + predicate isDBType() { type.getChild() instanceof QL::Dbtype } + + /** + * Gets the module of the type, if it exists. + * E.g. `DataFlow` in `DataFlow::Node`. + */ + ModuleExpr getModule() { toQL(result) = type.getChild() } + + /** + * Gets the type that this type reference refers to. + */ + Type getResolvedType() { resolveTypeExpr(this, result) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getModule") and result = this.getModule() + } +} + +/** + * A QL module. + */ +class Module extends TModule, ModuleDeclaration { + QL::Module mod; + + Module() { this = TModule(mod) } + + override string getAPrimaryQlClass() { result = "Module" } + + override string getName() { result = mod.getName().(QL::ModuleName).getChild().getValue() } + + /** + * Gets a member of the module. + */ + AstNode getAMember() { toQL(result) = mod.getChild(_).(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() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getAlias") and result = this.getAlias() + or + pred = directMember("getAMember") and result = this.getAMember() + } +} + +/** + * Something that can be member of a module. + */ +class ModuleMember extends TModuleMember, AstNode { + /** Holds if this member is declared as `private`. */ + predicate isPrivate() { this.hasAnnotation("private") } +} + +/** A declaration. E.g. a class, type, predicate, newtype... */ +class Declaration extends TDeclaration, AstNode { + /** Gets the name of this declaration. */ + string getName() { none() } + + override string toString() { result = this.getAPrimaryQlClass() + " " + this.getName() } + + override QLDoc getQLDoc() { + result = any(TopLevel m).getQLDocFor(this) + or + result = any(Module m).getQLDocFor(this) + or + result = any(Class c).getQLDocFor(this) + } + + override AstNode getAChild(string pred) { result = super.getAChild(pred) } +} + +/** An entity that can be declared in a module. */ +class ModuleDeclaration extends TModuleDeclaration, Declaration, ModuleMember { } + +/** An type declaration. Either a `class` or a `newtype`. */ +class TypeDeclaration extends TTypeDeclaration, Declaration { } + +/** + * A QL class. + */ +class Class extends TClass, TypeDeclaration, ModuleDeclaration { + QL::Dataclass cls; + + Class() { this = TClass(cls) } + + override string getAPrimaryQlClass() { result = "Class" } + + override string getName() { result = cls.getName().getValue() } + + /** + * Gets the charateristic predicate for this class. + */ + CharPred getCharPred() { toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) } + + AstNode getMember(int i) { toQL(result) = cls.getChild(i).(QL::ClassMember).getChild(_) } + + QLDoc getQLDocFor(AstNode m) { + exists(int i | result = this.getMember(i) and m = this.getMember(i + 1)) + } + + /** + * Gets a predicate in this class. + */ + ClassPredicate getAClassPredicate() { + toQL(result) = cls.getChild(_).(QL::ClassMember).getChild(_) + } + + /** + * Gets predicate `name` implemented in this class. + */ + ClassPredicate getClassPredicate(string name) { + result = this.getAClassPredicate() and + result.getName() = name + } + + /** + * Gets a field in this class. + */ + FieldDecl getAField() { result = getMember(_) } + + /** + * Gets a super-type referenced in the `extends` part of the class declaration. + */ + 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() } + + /** 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(_) } + + /** Gets the class type defined by this class declaration. */ + Type getType() { result.getDeclaration() = this } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getAliasType") and result = this.getAliasType() + or + pred = directMember("getUnionMember") and result = this.getUnionMember() + or + pred = directMember("getAField") and result = this.getAField() + or + pred = directMember("getCharPred") and result = this.getCharPred() + or + pred = directMember("getASuperType") and result = this.getASuperType() + or + pred = directMember("getAnInstanceofType") and result = this.getAnInstanceofType() + or + exists(string name | + pred = stringIndexedMember("getClassPredicate", name) and + result = this.getClassPredicate(name) + ) + } + + /** Holds if this class is abstract. */ + predicate isAbstract() { hasAnnotation("abstract") } +} + +/** + * A `newtype Foo` declaration. + */ +class NewType extends TNewType, TypeDeclaration, ModuleDeclaration { + QL::Datatype type; + + NewType() { this = TNewType(type) } + + override string getName() { result = type.getName().getValue() } + + override string getAPrimaryQlClass() { result = "NewType" } + + /** + * Gets a branch in this `newtype`. + */ + NewTypeBranch getABranch() { toQL(result) = type.getChild().getChild(_) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getABranch") and result = this.getABranch() + } +} + +/** + * A branch in a `newtype`. + * E.g. `Bar()` or `Baz()` in `newtype Foo = Bar() or Baz()`. + */ +class NewTypeBranch extends TNewTypeBranch, Predicate, TypeDeclaration { + QL::DatatypeBranch branch; + + NewTypeBranch() { this = TNewTypeBranch(branch) } + + override string getAPrimaryQlClass() { result = "NewTypeBranch" } + + override string getName() { result = branch.getName().getValue() } + + /** 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) + } + + /** Gets the body of this branch. */ + override Formula getBody() { toQL(result) = branch.getChild(_).(QL::Body).getChild() } + + override NewTypeBranchType getReturnType() { result.getDeclaration() = this } + + override Type getParameterType(int i) { result = this.getField(i).getType() } + + override int getArity() { result = count(this.getField(_)) } + + override Type getDeclaringType() { none() } + + override predicate isPrivate() { this.getNewType().isPrivate() } + + override QLDoc getQLDoc() { toQL(result) = branch.getChild(_) } + + NewType getNewType() { result.getABranch() = this } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getBody") and result = this.getBody() + or + exists(int i | pred = indexedMember("getField", i) and result = this.getField(i)) + } +} + +/** + * A call to a predicate. + * Either a predicate call `foo()`, + * or a member call `foo.bar()`, + * or a special call to `none()` or `any()`. + */ +class Call extends TCall, Expr, Formula { + /** Gets the `i`th argument of this call. */ + Expr getArgument(int i) { + none() // overriden in sublcasses. + } + + /** Gets an argument of this call, if any. */ + final Expr getAnArgument() { result = this.getArgument(_) } + + PredicateOrBuiltin getTarget() { resolveCall(this, result) } + + override Type getType() { result = this.getTarget().getReturnType() } + + final int getNumberOfArguments() { result = count(this.getArgument(_)) } + + /** Holds if this call is a transitive closure of `kind` either `+` or `*`. */ + predicate isClosure(string kind) { none() } + + /** + * Gets the module that contains the predicate. + * E.g. for `Foo::bar()` the result is `Foo`. + */ + ModuleExpr getQualifier() { none() } +} + +/** + * A call to a non-member predicate. + * E.g. `foo()` or `Foo::bar()`. + */ +class PredicateCall extends TPredicateCall, Call { + QL::CallOrUnqualAggExpr expr; + + PredicateCall() { this = TPredicateCall(expr) } + + override Expr getArgument(int i) { + exists(QL::CallBody body | body.getParent() = expr | toQL(result) = body.getChild(i)) + } + + final override ModuleExpr getQualifier() { + exists(QL::AritylessPredicateExpr ape | + ape.getParent() = expr and + toQL(result).getParent() = ape + ) + } + + override string getAPrimaryQlClass() { result = "PredicateCall" } + + override predicate isClosure(string kind) { kind = expr.getChild(_).(QL::Closure).getValue() } + + /** + * Gets the name of the predicate called. + * E.g. for `foo()` the result is "foo". + */ + string getPredicateName() { + result = expr.getChild(0).(QL::AritylessPredicateExpr).getName().getValue() + } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | pred = indexedMember("getArgument", i) and result = this.getArgument(i)) + or + pred = directMember("getQualifier") and result = this.getQualifier() + } +} + +/** + * A member call to a predicate. + * E.g. `foo.bar()`. + */ +class MemberCall extends TMemberCall, Call { + QL::QualifiedExpr expr; + + MemberCall() { this = TMemberCall(expr) } + + override string getAPrimaryQlClass() { result = "MemberCall" } + + /** + * 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() } + + override predicate isClosure(string kind) { + kind = expr.getChild(_).(QL::QualifiedRhs).getChild(_).(QL::Closure).getValue() + } + + /** + * Gets the supertype referenced in this call, that is the `Foo` in `Foo.super.bar(...)`. + * + * Only yields a result if this is actually a `super` call. + */ + TypeExpr getSuperType() { toQL(result) = expr.getChild(_).(QL::SuperRef).getChild(0) } + + override Expr getArgument(int i) { + result = + rank[i + 1](Expr e, int index | + toQL(e) = expr.getChild(_).(QL::QualifiedRhs).getChild(index) + | + e order by index + ) + } + + /** + * Gets the base of the member call. + * E.g. for `foo.(Bar).baz()` the result is `foo.(Bar)`. + */ + Expr getBase() { toQL(result) = expr.getChild(0) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getBase") and result = this.getBase() + or + pred = directMember("getSuperType") and result = this.getSuperType() + or + exists(int i | pred = indexedMember("getArgument", i) and result = this.getArgument(i)) + } +} + +/** + * A call to the special `none()` predicate. + */ +class NoneCall extends TNoneCall, Call, Formula { + QL::SpecialCall call; + + NoneCall() { this = TNoneCall(call) } + + override string getAPrimaryQlClass() { result = "NoneCall" } + + override AstNode getParent() { result = Call.super.getParent() } +} + +/** + * A call to the special `any()` predicate. + */ +class AnyCall extends TAnyCall, Call { + QL::Aggregate agg; + + AnyCall() { this = TAnyCall(agg) } + + override string getAPrimaryQlClass() { result = "AnyCall" } +} + +/** + * An inline cast, e.g. `foo.(Bar)`. + */ +class InlineCast extends TInlineCast, Expr { + QL::QualifiedExpr expr; + + InlineCast() { this = TInlineCast(expr) } + + override string getAPrimaryQlClass() { result = "InlineCast" } + + /** + * Gets the type being cast to. + * E.g. for `foo.(Bar)` the result is `Bar`. + */ + TypeExpr getTypeExpr() { toQL(result) = expr.getChild(_).(QL::QualifiedRhs).getChild(_) } + + override Type getType() { result = this.getTypeExpr().getResolvedType() } + + /** + * Gets the expression being cast. + * E.g. for `foo.(Bar)` the result is `foo`. + */ + Expr getBase() { toQL(result) = expr.getChild(0) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getTypeExpr") and result = this.getTypeExpr() + or + pred = directMember("getBase") and result = this.getBase() + } +} + +/** An entity that resolves to a module. */ +class ModuleRef extends AstNode, TModuleRef { + /** Gets the module that this entity resolves to. */ + FileOrModule getResolvedModule() { none() } +} + +/** + * An import statement. + */ +class Import extends TImport, ModuleMember, ModuleRef { + QL::ImportDirective imp; + + Import() { this = TImport(imp) } + + override string getAPrimaryQlClass() { result = "Import" } + + /** + * Gets the name under which this import is imported, if such a name exists. + * E.g. the `Flow` in: + * ``` + * import semmle.javascript.dataflow.Configuration as Flow + * ``` + */ + string importedAs() { result = imp.getChild(1).(QL::ModuleName).getChild().getValue() } + + /** + * Gets the `i`th selected name from the imported module. + * E.g. for + * `import foo.bar::Baz::Qux` + * It is true that `getSelectionName(0) = "Baz"` and `getSelectionName(1) = "Qux"`. + */ + string getSelectionName(int i) { + result = imp.getChild(0).(QL::ImportModuleExpr).getName(i).getValue() + } + + /** + * Gets the `i`th imported module. + * E.g. for + * `import foo.bar::Baz::Qux` + * It is true that `getQualifiedName(0) = "foo"` and `getQualifiedName(1) = "bar"`. + */ + string getQualifiedName(int i) { + result = imp.getChild(0).(QL::ImportModuleExpr).getChild().getName(i).getValue() + } + + final override FileOrModule getResolvedModule() { resolve(this, result) } +} + +/** A formula, such as `x = 6 and y < 5`. */ +class Formula extends TFormula, AstNode { } + +/** An `and` formula, with 2 or more operands. */ +class Conjunction extends TConjunction, AstNode, Formula { + QL::Conjunction conj; + + Conjunction() { this = TConjunction(conj) } + + override string getAPrimaryQlClass() { result = "Conjunction" } + + /** Gets an operand to this conjunction. */ + Formula getAnOperand() { toQL(result) in [conj.getLeft(), conj.getRight()] } + + /** Gets the left operand to this conjunction. */ + Formula getLeft() { toQL(result) = conj.getLeft() } + + /** Gets the right operand to this conjunction. */ + Formula getRight() { toQL(result) = conj.getRight() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getAnOperand") and result = this.getAnOperand() + } +} + +/** An `or` formula, with 2 or more operands. */ +class Disjunction extends TDisjunction, AstNode, Formula { + QL::Disjunction disj; + + Disjunction() { this = TDisjunction(disj) } + + override string getAPrimaryQlClass() { result = "Disjunction" } + + /** Gets an operand to this disjunction. */ + Formula getAnOperand() { toQL(result) in [disj.getLeft(), disj.getRight()] } + + /** Gets the left operand to this disjunction */ + Formula getLeft() { toQL(result) = disj.getLeft() } + + /** Gets the right operand to this disjunction */ + Formula getRight() { toQL(result) = disj.getRight() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getAnOperand") and result = this.getAnOperand() + } +} + +/** + * A literal expression, such as `6` or `true` or `"foo"`. + */ +class Literal extends TLiteral, Expr { + QL::Literal lit; + + Literal() { this = TLiteral(lit) } + + override string getAPrimaryQlClass() { result = "??Literal??" } +} + +/** A string literal. */ +class String extends Literal { + String() { lit.getChild() instanceof QL::String } + + override string getAPrimaryQlClass() { result = "String" } + + override PrimitiveType getType() { result.getName() = "string" } + + /** Gets the string value of this literal. */ + string getValue() { + exists(string raw | raw = lit.getChild().(QL::String).getValue() | + result = raw.substring(1, raw.length() - 1) + ) + } +} + +/** An integer literal. */ +class Integer extends Literal { + 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().(QL::Integer).getValue().toInt() } +} + +/** A float literal. */ +class Float extends Literal { + 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().(QL::Float).getValue().toFloat() } +} + +/** A boolean literal */ +class Boolean extends Literal { + QL::Bool bool; + + Boolean() { lit.getChild() = bool } + + /** Holds if the value is `true` */ + predicate isTrue() { bool.getChild() instanceof QL::True } + + /** Holds if the value is `false` */ + predicate isFalse() { bool.getChild() instanceof QL::False } + + override PrimitiveType getType() { result.getName() = "boolean" } + + override string getAPrimaryQlClass() { result = "Boolean" } +} + +/** A comparison symbol, such as `"<"` or `"="`. */ +class ComparisonSymbol extends string { + ComparisonSymbol() { + this = "=" or + this = "!=" or + this = "<" or + this = ">" or + this = "<=" or + this = ">=" + } +} + +/** A comparison formula, such as `x < 3` or `y = true`. */ +class ComparisonFormula extends TComparisonFormula, Formula { + QL::CompTerm comp; + + ComparisonFormula() { this = TComparisonFormula(comp) } + + /** Gets the left operand of this comparison. */ + Expr getLeftOperand() { toQL(result) = comp.getLeft() } + + /** Gets the right operand of this comparison. */ + Expr getRightOperand() { toQL(result) = comp.getRight() } + + /** Gets an operand of this comparison. */ + Expr getAnOperand() { result in [this.getLeftOperand(), this.getRightOperand()] } + + /** Gets the operator of this comparison. */ + ComparisonSymbol getOperator() { result = comp.getChild().getValue() } + + override string getAPrimaryQlClass() { result = "ComparisonFormula" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getLeftOperand") and result = this.getLeftOperand() + or + pred = directMember("getRightOperand") and result = this.getRightOperand() + } +} + +/** A quantifier formula, such as `exists` or `forall`. */ +class Quantifier extends TQuantifier, Formula { + QL::Quantified quant; + string kind; + + Quantifier() { + 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 + 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() { 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() { toQL(result) = quant.getFormula() } + + /** + * Gets the expression of this quantifier, if the quantifier is + * of the form `exists( expr )`. + */ + Expr getExpr() { toQL(result) = quant.getExpr() } + + /** + * 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(this.getExpr()) } + + override string getAPrimaryQlClass() { result = "Quantifier" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | pred = indexedMember("getArgument", i) and result = this.getArgument(i)) + or + pred = directMember("getRange") and result = this.getRange() + or + pred = directMember("getFormula") and result = this.getFormula() + or + pred = directMember("getExpr") and result = this.getExpr() + } +} + +/** An `exists` quantifier. */ +class Exists extends Quantifier { + Exists() { kind = "exists" } + + override string getAPrimaryQlClass() { result = "Exists" } +} + +/** A `forall` quantifier. */ +class Forall extends Quantifier { + Forall() { kind = "forall" } + + override string getAPrimaryQlClass() { result = "Forall" } +} + +/** A `forex` quantifier. */ +class Forex extends Quantifier { + Forex() { kind = "forex" } + + override string getAPrimaryQlClass() { result = "Forex" } +} + +/** A conditional formula, of the form `if a then b else c`. */ +class IfFormula extends TIfFormula, Formula { + QL::IfTerm ifterm; + + IfFormula() { this = TIfFormula(ifterm) } + + /** Gets the condition (the `if` part) of this formula. */ + Formula getCondition() { toQL(result) = ifterm.getCond() } + + /** Gets the `then` part of this formula. */ + Formula getThenPart() { toQL(result) = ifterm.getFirst() } + + /** Gets the `else` part of this formula. */ + Formula getElsePart() { toQL(result) = ifterm.getSecond() } + + override string getAPrimaryQlClass() { result = "IfFormula" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getCondition") and result = this.getCondition() + or + pred = directMember("getThenPart") and result = this.getThenPart() + or + pred = directMember("getElsePart") and result = this.getElsePart() + } +} + +/** + * An implication formula, of the form `foo implies bar`. + */ +class Implication extends TImplication, Formula { + QL::Implication imp; + + Implication() { this = TImplication(imp) } + + /** Gets the left operand of this implication. */ + Formula getLeftOperand() { toQL(result) = imp.getLeft() } + + /** Gets the right operand of this implication. */ + Formula getRightOperand() { toQL(result) = imp.getRight() } + + override string getAPrimaryQlClass() { result = "Implication" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getLeftOperand") and result = this.getLeftOperand() + or + pred = directMember("getRightOperand") and result = this.getRightOperand() + } +} + +/** + * A type check formula, of the form `foo instanceof bar`. + */ +class InstanceOf extends TInstanceOf, Formula { + QL::InstanceOf inst; + + InstanceOf() { this = TInstanceOf(inst) } + + /** Gets the expression being checked. */ + Expr getExpr() { toQL(result) = inst.getChild(0) } + + /** Gets the reference to the type being checked. */ + TypeExpr getType() { toQL(result) = inst.getChild(1) } + + override string getAPrimaryQlClass() { result = "InstanceOf" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getExpr") and result = this.getExpr() + or + pred = directMember("getType") and result = this.getType() + } +} + +/** + * A in formula, such as `foo in [2, 3]`. + * The formula holds if the lhs is in the rhs. + */ +class InFormula extends TInFormula, Formula { + QL::InExpr inexpr; + + InFormula() { this = TInFormula(inexpr) } + + /** + * Gets the expression that is checked for membership. + * E.g. for `foo in [2, 3]` the result is `foo`. + */ + 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() { toQL(result) = inexpr.getRight() } + + override string getAPrimaryQlClass() { result = "InFormula" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getExpr") and result = this.getExpr() + or + pred = directMember("getRange") and result = this.getRange() + } +} + +/** + * A "call" to a high-order formula. + * E.g. `fastTC(pathSucc/2)(n1, n2)`. + */ +class HigherOrderFormula extends THigherOrderFormula, Formula { + QL::HigherOrderTerm hop; + + HigherOrderFormula() { this = THigherOrderFormula(hop) } + + /** + * 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) { toQL(result) = hop.getChild(i).(QL::PredicateExpr) } + + /** + * Gets the number of inputs. + */ + private int getNumInputs() { result = 1 + max(int i | exists(this.getInput(i))) } + + /** + * 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) { toQL(result) = hop.getChild(i + this.getNumInputs()) } + + /** + * Gets the name of this higher-order predicate. + * E.g. for `fastTC(pathSucc/2)(n1, n2)` the result is "fastTC". + */ + string getName() { result = hop.getName().getValue() } + + override string getAPrimaryQlClass() { result = "HigherOrderFormula" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | + pred = indexedMember("getInput", i) and result = this.getInput(i) + or + pred = indexedMember("getArgument", i) and result = this.getArgument(i) + ) + } +} + +class Aggregate extends TAggregate, Expr { + string getKind() { none() } + + QL::Aggregate getAggregate() { none() } +} + +/** + * An aggregate containing an expression. + * E.g. `min(getAPredicate().getArity())`. + */ +class ExprAggregate extends TExprAggregate, Aggregate { + QL::Aggregate agg; + QL::ExprAggregateBody body; + string kind; + + ExprAggregate() { + this = TExprAggregate(agg) and + kind = agg.getChild(0).(QL::AggId).getValue() and + body = agg.getChild(_) + } + + /** + * Gets the kind of aggregate. + * E.g. for `min(foo())` the result is "min". + */ + override string getKind() { result = kind } + + override QL::Aggregate getAggregate() { result = agg } + + /** + * Gets the ith "as" expression of this aggregate, if any. + */ + 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) { 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).(QL::Direction).getValue() + } + + override string getAPrimaryQlClass() { result = "ExprAggregate[" + kind + "]" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | + pred = indexedMember("getExpr", i) and result = this.getExpr(i) + or + pred = indexedMember("getOrderBy", i) and result = this.getOrderBy(i) + ) + } + + override Type getType() { + exists(PrimitiveType prim | prim = result | + kind.regexpMatch("(strict)?count") and + result.getName() = "int" + or + kind.regexpMatch("(strict)?concat") and + result.getName() = "string" + ) + or + not kind = ["count", "strictcount"] and + result = this.getExpr(0).getType() + } +} + +/** An aggregate expression, such as `count` or `sum`. */ +class FullAggregate extends TFullAggregate, Aggregate { + QL::Aggregate agg; + string kind; + QL::FullAggregateBody body; + + FullAggregate() { + this = TFullAggregate(agg) and + kind = agg.getChild(0).(QL::AggId).getValue() and + body = agg.getChild(_) + } + + /** + * Gets the kind of aggregate. + * E.g. for `min(int i | foo(i))` the result is "foo". + */ + override string getKind() { result = kind } + + override QL::Aggregate getAggregate() { result = agg } + + /** Gets the ith declared argument of this quantifier. */ + VarDecl getArgument(int i) { toQL(result) = body.getChild(i) } + + /** Gets an argument of this quantifier. */ + VarDecl getAnArgument() { result = this.getArgument(_) } + + /** + * Gets the formula restricting the range of this quantifier, if any. + */ + Formula getRange() { toQL(result) = body.getGuard() } + + /** + * Gets the ith "as" expression of this aggregate, if any. + */ + 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) { 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).(QL::Direction).getValue() + } + + override string getAPrimaryQlClass() { kind != "rank" and result = "FullAggregate[" + kind + "]" } + + override Type getType() { + exists(PrimitiveType prim | prim = result | + kind = ["count", "strictcount"] and + result.getName() = "int" + or + kind.regexpMatch("(strict)?concat") and + result.getName() = "string" + ) + or + kind = ["any", "min", "max", "unique", "rank", "sum", "strictsum"] and + not exists(this.getExpr(_)) and + result = this.getArgument(0).getType() + or + not kind = ["count", "strictcount"] and + result = this.getExpr(0).getType() + } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | + pred = indexedMember("getArgument", i) and result = this.getArgument(i) + or + pred = indexedMember("getExpr", i) and result = this.getExpr(i) + or + pred = indexedMember("getOrderBy", i) and result = this.getOrderBy(i) + ) + or + pred = directMember("getRange") and result = this.getRange() + } +} + +/** + * A "rank" expression, such as `rank[4](int i | i = [5 .. 15] | i)`. + */ +class Rank extends Aggregate { + Rank() { this.getKind() = "rank" } + + override string getAPrimaryQlClass() { result = "Rank" } + + /** + * The `i` in `rank[i]( | | )`. + */ + Expr getRankExpr() { toQL(result) = this.getAggregate().getChild(1) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getRankExpr") and result = this.getRankExpr() + } +} + +/** + * An "as" expression, such as `foo as bar`. + */ +class AsExpr extends TAsExpr, VarDef, Expr { + QL::AsExpr asExpr; + + AsExpr() { this = TAsExpr(asExpr) } + + override string getAPrimaryQlClass() { result = "AsExpr" } + + final override string getName() { result = this.getAsName() } + + final override Type getType() { result = this.getInnerExpr().getType() } + + /** + * 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).(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() { toQL(result) = asExpr.getChild(0) } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getInnerExpr") and result = this.getInnerExpr() + } +} + +/** + * An identifier, such as `foo`. + */ +class Identifier extends TIdentifier, Expr { + QL::Variable id; + + Identifier() { this = TIdentifier(id) } + + string getName() { none() } + + final override string toString() { result = this.getName() } + + override string getAPrimaryQlClass() { result = "Identifier" } +} + +/** An access to a variable. */ +class VarAccess extends Identifier { + private VarDef decl; + + VarAccess() { resolveVariable(this, decl) } + + /** Gets the accessed variable. */ + VarDef getDeclaration() { result = decl } + + override string getName() { result = id.getChild().(QL::VarName).getChild().getValue() } + + override Type getType() { result = this.getDeclaration().getType() } + + override string getAPrimaryQlClass() { result = "VarAccess" } +} + +/** An access to a field. */ +class FieldAccess extends Identifier { + private VarDecl decl; + + FieldAccess() { resolveField(this, decl) } + + /** Gets the accessed field. */ + VarDecl getDeclaration() { result = decl } + + override string getName() { result = id.getChild().(QL::VarName).getChild().getValue() } + + override Type getType() { result = this.getDeclaration().getType() } + + override string getAPrimaryQlClass() { result = "FieldAccess" } +} + +/** An access to `this`. */ +class ThisAccess extends Identifier { + ThisAccess() { any(QL::This t).getParent() = id } + + override Type getType() { result = this.getParent+().(Class).getType() } + + override string getName() { result = "this" } + + override string getAPrimaryQlClass() { result = "ThisAccess" } +} + +/** A use of `super`. */ +class Super extends TSuper, Expr { + Super() { this = TSuper(_) } + + override string getAPrimaryQlClass() { result = "Super" } + + override Type getType() { + exists(MemberCall call | call.getBase() = this | result = call.getTarget().getDeclaringType()) + } +} + +/** An access to `result`. */ +class ResultAccess extends Identifier { + ResultAccess() { any(QL::Result r).getParent() = id } + + override Type getType() { result = this.getParent+().(Predicate).getReturnType() } + + override string getName() { result = "result" } + + override string getAPrimaryQlClass() { result = "ResultAccess" } +} + +/** A `not` formula. */ +class Negation extends TNegation, Formula { + QL::Negation neg; + + Negation() { this = TNegation(neg) } + + /** Gets the formula being negated. */ + Formula getFormula() { toQL(result) = neg.getChild() } + + override string getAPrimaryQlClass() { result = "Negation" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getFormula") and result = this.getFormula() + } +} + +/** An expression, such as `x+4`. */ +class Expr extends TExpr, AstNode { + cached + Type getType() { none() } +} + +/** An expression annotation, such as `pragma[only_bind_into](config)`. */ +class ExprAnnotation extends TExprAnnotation, Expr { + QL::ExprAnnotation expr_anno; + + ExprAnnotation() { this = TExprAnnotation(expr_anno) } + + /** + * Gets the name of the annotation. + * E.g. for `pragma[only_bind_into](config)` the result is "pragma". + */ + string getName() { result = expr_anno.getName().getValue() } + + /** + * Gets the argument to this annotation. + * E.g. for `pragma[only_bind_into](config)` the result is "only_bind_into". + */ + string getAnnotationArgument() { result = expr_anno.getAnnotArg().getValue() } + + /** + * Gets the inner expression. + * E.g. for `pragma[only_bind_into](config)` the result is `config`. + */ + Expr getExpression() { toQL(result) = expr_anno.getChild() } + + override Type getType() { result = this.getExpression().getType() } + + override string getAPrimaryQlClass() { result = "ExprAnnotation" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getExpression") and result = this.getExpression() + } +} + +/** A function symbol, such as `+` or `*`. */ +class FunctionSymbol extends string { + FunctionSymbol() { this = "+" or this = "-" or this = "*" or this = "/" or this = "%" } +} + +/** + * A binary operation expression, such as `x + 3` or `y / 2`. + */ +class BinOpExpr extends TBinOpExpr, Expr { + /** Gets the left operand of the binary expression. */ + Expr getLeftOperand() { none() } // overriden in subclasses + + /* Gets the right operand of the binary expression. */ + Expr getRightOperand() { none() } // overriden in subclasses + + /** Gets the operator of the binary expression. */ + FunctionSymbol getOperator() { none() } // overriden in subclasses + + /* Gets an operand of the binary expression. */ + final Expr getAnOperand() { result = this.getLeftOperand() or result = this.getRightOperand() } +} + +/** + * An addition or subtraction expression. + */ +class AddSubExpr extends TAddSubExpr, BinOpExpr { + QL::AddExpr expr; + FunctionSymbol operator; + + AddSubExpr() { this = TAddSubExpr(expr) and operator = expr.getChild().getValue() } + + override Expr getLeftOperand() { toQL(result) = expr.getLeft() } + + override Expr getRightOperand() { toQL(result) = expr.getRight() } + + override FunctionSymbol getOperator() { result = operator } + + override PrimitiveType getType() { + // Both operands are the same type + result = this.getLeftOperand().getType() and + result = this.getRightOperand().getType() + or + // Both operands are subtypes of `int` / `string` / `float` + exprOfPrimitiveAddType(result) = this.getLeftOperand() and + exprOfPrimitiveAddType(result) = this.getRightOperand() + or + // Coercion to from `int` to `float` + exists(PrimitiveType i | result.getName() = "float" and i.getName() = "int" | + this.getAnOperand().getType() = result and + this.getAnOperand() = exprOfPrimitiveAddType(i) + ) + or + // Coercion to `string` + result.getName() = "string" and + this.getAnOperand().getType() = result + } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getLeftOperand") and result = this.getLeftOperand() + or + pred = directMember("getRightOperand") and result = this.getRightOperand() + } +} + +pragma[noinline] +private Expr exprOfPrimitiveAddType(PrimitiveType t) { + result.getType() = getASubTypeOfAddPrimitive(t) +} + +/** + * Gets a subtype of the given primitive type `prim`. + * This predicate does not consider float to be a supertype of int. + */ +private Type getASubTypeOfAddPrimitive(PrimitiveType prim) { + result = prim and + result.getName() = ["int", "string", "float"] + or + exists(Type superType | superType = getASubTypeOfAddPrimitive(prim) | + result.getASuperType() = superType and + not (result.getName() = "int" and superType.getName() = "float") + ) +} + +/** + * An addition expression, such as `x + y`. + */ +class AddExpr extends AddSubExpr { + AddExpr() { operator = "+" } + + override string getAPrimaryQlClass() { result = "AddExpr" } +} + +/** + * A subtraction expression, such as `x - y`. + */ +class SubExpr extends AddSubExpr { + SubExpr() { operator = "-" } + + override string getAPrimaryQlClass() { result = "SubExpr" } +} + +/** + * A multiplication, division, or modulo expression. + */ +class MulDivModExpr extends TMulDivModExpr, BinOpExpr { + 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() { toQL(result) = expr.getLeft() } + + /** Gets the right operand of the binary expression. */ + override Expr getRightOperand() { toQL(result) = expr.getRight() } + + override FunctionSymbol getOperator() { result = operator } + + override PrimitiveType getType() { + // Both operands are of the same type + this.getLeftOperand().getType() = result and + this.getRightOperand().getType() = result + or + // Both operands are subtypes of `int`/`float` + result.getName() = ["int", "float"] and + exprOfPrimitiveAddType(result) = this.getLeftOperand() and + exprOfPrimitiveAddType(result) = this.getRightOperand() + or + // Coercion from `int` to `float` + exists(PrimitiveType i | result.getName() = "float" and i.getName() = "int" | + this.getLeftOperand() = exprOfPrimitiveAddType(result) and + this.getRightOperand() = exprOfPrimitiveAddType(i) + or + this.getRightOperand() = exprOfPrimitiveAddType(result) and + this.getLeftOperand() = exprOfPrimitiveAddType(i) + ) + } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getLeftOperand") and result = this.getLeftOperand() + or + pred = directMember("getRightOperand") and result = this.getRightOperand() + } +} + +/** + * A division expression, such as `x / y`. + */ +class DivExpr extends MulDivModExpr { + DivExpr() { operator = "/" } + + override string getAPrimaryQlClass() { result = "DivExpr" } +} + +/** + * A multiplication expression, such as `x * y`. + */ +class MulExpr extends MulDivModExpr { + MulExpr() { operator = "*" } + + override string getAPrimaryQlClass() { result = "MulExpr" } +} + +/** + * A modulo expression, such as `x % y`. + */ +class ModExpr extends MulDivModExpr { + ModExpr() { operator = "%" } + + override string getAPrimaryQlClass() { result = "ModExpr" } +} + +/** + * A range expression, such as `[1 .. 10]`. + */ +class Range extends TRange, Expr { + QL::Range range; + + Range() { this = TRange(range) } + + /** + * Gets the lower bound of the range. + */ + Expr getLowEndpoint() { toQL(result) = range.getLower() } + + /** + * Gets the upper bound of the range. + */ + Expr getHighEndpoint() { toQL(result) = range.getUpper() } + + /** + * Gets the lower and upper bounds of the range. + */ + Expr getAnEndpoint() { result = [this.getLowEndpoint(), this.getHighEndpoint()] } + + override PrimitiveType getType() { result.getName() = "int" } + + override string getAPrimaryQlClass() { result = "Range" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getLowEndpoint") and result = this.getLowEndpoint() + or + pred = directMember("getHighEndpoint") and result = this.getHighEndpoint() + } +} + +/** + * A set literal expression, such as `[1,3,5,7]`. + */ +class Set extends TSet, Expr { + QL::SetLiteral set; + + Set() { this = TSet(set) } + + /** + * Gets the `i`th element in this set literal expression. + */ + Expr getElement(int i) { toQL(result) = set.getChild(i) } + + /** + * Gets an element in this set literal expression, if any. + */ + Expr getAnElement() { result = this.getElement(_) } + + /** + * Gets the number of elements in this set literal expression. + */ + int getNumberOfElements() { result = count(this.getAnElement()) } + + override Type getType() { result = this.getElement(0).getType() } + + override string getAPrimaryQlClass() { result = "Set" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | pred = indexedMember("getElement", i) and result = this.getElement(i)) + } +} + +/** A unary operation expression, such as `-(x*y)` */ +class UnaryExpr extends TUnaryExpr, Expr { + QL::UnaryExpr unaryexpr; + + UnaryExpr() { this = TUnaryExpr(unaryexpr) } + + /** Gets the operand of the unary expression. */ + Expr getOperand() { toQL(result) = unaryexpr.getChild(1) } + + /** Gets the operator of the unary expression as a string. */ + FunctionSymbol getOperator() { result = unaryexpr.getChild(0).toString() } + + override Type getType() { result = this.getOperand().getType() } + + override string getAPrimaryQlClass() { result = "UnaryExpr" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getOperand") and result = this.getOperand() + } +} + +/** A "don't care" expression, denoted by `_`. */ +class DontCare extends TDontCare, Expr { + QL::Underscore dontcare; + + DontCare() { this = TDontCare(dontcare) } + + override DontCareType getType() { any() } + + override string getAPrimaryQlClass() { result = "DontCare" } +} + +/** A module expression. Such as `DataFlow` in `DataFlow::Node` */ +class ModuleExpr extends TModuleExpr, ModuleRef { + QL::ModuleExpr me; + + ModuleExpr() { this = TModuleExpr(me) } + + /** + * Gets the name of this module expression. For example, the name of + * + * ```ql + * Foo::Bar + * ``` + * + * is `Bar`. + */ + string getName() { + result = me.getName().getValue() + or + not exists(me.getName()) and result = me.getChild().(QL::SimpleId).getValue() + } + + /** + * Gets the qualifier of this module expression. For example, the qualifier of + * + * ```ql + * Foo::Bar::Baz + * ``` + * + * is `Foo::Bar`. + */ + ModuleExpr getQualifier() { result = TModuleExpr(me.getChild()) } + + final override FileOrModule getResolvedModule() { resolveModuleExpr(this, result) } + + final override string toString() { result = this.getName() } + + override string getAPrimaryQlClass() { result = "ModuleExpr" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = directMember("getQualifier") and result = this.getQualifier() + } +} + +/** An argument to an annotation. */ +private class AnnotationArg extends TAnnotationArg, AstNode { + QL::AnnotArg arg; + + AnnotationArg() { this = TAnnotationArg(arg) } + + /** Gets the name of this argument. */ + string getValue() { + result = + [ + arg.getChild().(QL::SimpleId).getValue(), arg.getChild().(QL::Result).getValue(), + arg.getChild().(QL::This).getValue() + ] + } + + override string toString() { result = this.getValue() } +} + +private class NoInlineArg extends AnnotationArg { + NoInlineArg() { this.getValue() = "noinline" } +} + +private class NoMagicArg extends AnnotationArg { + NoMagicArg() { this.getValue() = "nomagic" } +} + +private class InlineArg extends AnnotationArg { + InlineArg() { this.getValue() = "inline" } +} + +private class NoOptArg extends AnnotationArg { + NoOptArg() { this.getValue() = "noopt" } +} + +private class MonotonicAggregatesArg extends AnnotationArg { + MonotonicAggregatesArg() { this.getValue() = "monotonicAggregates" } +} + +/** An annotation on an element. */ +class Annotation extends TAnnotation, AstNode { + QL::Annotation annot; + + Annotation() { this = TAnnotation(annot) } + + override string toString() { result = "annotation" } + + override string getAPrimaryQlClass() { result = "Annotation" } + + override Location getLocation() { result = annot.getLocation() } + + /** Gets the node corresponding to the field `args`. */ + AnnotationArg getArgs(int i) { toQL(result) = annot.getArgs(i) } + + /** Gets the node corresponding to the field `name`. */ + string getName() { result = annot.getName().getValue() } + + override AstNode getParent() { result = AstNode.super.getParent() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + exists(int i | pred = indexedMember("getArgs", i) and result = this.getArgs(i)) + } +} + +/** A `pragma[noinline]` annotation. */ +class NoInline extends Annotation { + NoInline() { this.getArgs(0) instanceof NoInlineArg } + + override string toString() { result = "noinline" } +} + +/** A `pragma[inline]` annotation. */ +class Inline extends Annotation { + Inline() { this.getArgs(0) instanceof InlineArg } + + override string toString() { result = "inline" } +} + +/** A `pragma[nomagic]` annotation. */ +class NoMagic extends Annotation { + NoMagic() { this.getArgs(0) instanceof NoMagicArg } + + override string toString() { result = "nomagic" } +} + +/** A `pragma[noopt]` annotation. */ +class NoOpt extends Annotation { + NoOpt() { this.getArgs(0) instanceof NoOptArg } + + override string toString() { result = "noopt" } +} + +/** A `language[monotonicAggregates]` annotation. */ +class MonotonicAggregates extends Annotation { + MonotonicAggregates() { this.getArgs(0) instanceof MonotonicAggregatesArg } + + override string toString() { result = "monotonicaggregates" } +} + +/** A `bindingset` annotation. */ +class BindingSet extends Annotation { + BindingSet() { this.getName() = "bindingset" } + + /** Gets the `index`'th bound name in this bindingset. */ + string getBoundName(int index) { result = this.getArgs(index).getValue() } + + /** Gets a name bound by this bindingset, if any. */ + string getABoundName() { result = this.getBoundName(_) } + + /** Gets the number of names bound by this bindingset. */ + int getNumberOfBoundNames() { result = count(this.getABoundName()) } +} + +/** + * Classes modelling YAML AST nodes. + */ +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(this.getParent()) } + } + + /** A YAML comment. */ + class YAMLComment extends TYamlCommemt, YAMLNode { + QL::YamlComment yamlcomment; + + YAMLComment() { this = TYamlCommemt(yamlcomment) } + + override string getAPrimaryQlClass() { result = "YAMLComment" } + } + + /** A YAML entry. */ + class YAMLEntry extends TYamlEntry, YAMLNode { + QL::YamlEntry yamle; + + YAMLEntry() { this = TYamlEntry(yamle) } + + /** Gets the key of this YAML entry. */ + YAMLKey getKey() { + exists(QL::YamlKeyvaluepair pair | + pair.getParent() = yamle and + result = TYamlKey(pair.getKey()) + ) + } + + YAMLListItem getListItem() { toQL(result).getParent() = yamle } + + /** Gets the value of this YAML entry. */ + YAMLValue getValue() { + exists(QL::YamlKeyvaluepair pair | + pair.getParent() = yamle and + result = TYamlValue(pair.getValue()) + ) + } + + override string getAPrimaryQlClass() { result = "YAMLEntry" } + } + + /** A YAML key. */ + class YAMLKey extends TYamlKey, YAMLNode { + QL::YamlKey yamlkey; + + YAMLKey() { this = TYamlKey(yamlkey) } + + /** + * Gets the value of this YAML key. + */ + YAMLValue getValue() { + exists(QL::YamlKeyvaluepair pair | + pair.getKey() = yamlkey and result = TYamlValue(pair.getValue()) + ) + } + + override string getAPrimaryQlClass() { result = "YAMLKey" } + + /** Gets the value of this YAML value. */ + string getNamePart(int i) { + i = 0 and result = yamlkey.getChild(0).(QL::SimpleId).getValue() + or + exists(YAMLKey child | + child = TYamlKey(yamlkey.getChild(1)) and + result = child.getNamePart(i - 1) + ) + } + + /** + * Gets all the name parts of this YAML key concatenated with `/`. + * Dashes are replaced with `/` (because we don't have that information in the generated AST). + */ + string getQualifiedName() { + result = concat(string part, int i | part = this.getNamePart(i) | part, "/" order by i) + } + } + + /** A YAML list item. */ + class YAMLListItem extends TYamlListitem, YAMLNode { + QL::YamlListitem yamllistitem; + + YAMLListItem() { this = TYamlListitem(yamllistitem) } + + /** + * Gets the value of this YAML list item. + */ + YAMLValue getValue() { result = TYamlValue(yamllistitem.getChild()) } + + override string getAPrimaryQlClass() { result = "YAMLListItem" } + } + + /** A YAML value. */ + class YAMLValue extends TYamlValue, YAMLNode { + QL::YamlValue yamlvalue; + + YAMLValue() { this = TYamlValue(yamlvalue) } + + override string getAPrimaryQlClass() { result = "YAMLValue" } + + /** Gets the value of this YAML value. */ + string getValue() { result = yamlvalue.getValue() } + } + + // to not expose the entire `File` API on `QlPack`. + private newtype TQLPack = MKQlPack(File file) { file.getBaseName() = "qlpack.yml" } + + /** + * A `qlpack.yml` file. + */ + class QLPack extends MKQlPack { + File file; + + QLPack() { this = MKQlPack(file) } + + private string getProperty(string name) { + exists(YAMLEntry entry | + entry.isRoot() and + entry.getKey().getQualifiedName() = name and + result = entry.getValue().getValue().trim() and + entry.getLocation().getFile() = file + ) + } + + /** Gets the name of this qlpack */ + string getName() { result = this.getProperty("name") } + + /** Gets the version of this qlpack */ + string getVersion() { result = this.getProperty("version") } + + /** Gets the extractor of this qlpack */ + string getExtractor() { result = this.getProperty("extractor") } + + string toString() { result = this.getName() } + + /** Gets the file that this `QLPack` represents. */ + File getFile() { result = file } + + private predicate isADependency(YAMLEntry entry) { + exists(YAMLEntry deps | + deps.getLocation().getFile() = file and entry.getLocation().getFile() = file + | + deps.isRoot() and + deps.getKey().getQualifiedName() = ["dependencies", "libraryPathDependencies"] and + entry.getLocation().getStartLine() = 1 + deps.getLocation().getStartLine() and + entry.getLocation().getStartColumn() > deps.getLocation().getStartColumn() + ) + or + exists(YAMLEntry prev | this.isADependency(prev) | + prev.getLocation().getFile() = file and + entry.getLocation().getFile() = file and + entry.getLocation().getStartLine() = 1 + prev.getLocation().getStartLine() and + entry.getLocation().getStartColumn() = prev.getLocation().getStartColumn() + ) + } + + predicate hasDependency(string name, string version) { + exists(YAMLEntry entry | this.isADependency(entry) | + entry.getKey().getQualifiedName().trim() = name and + entry.getValue().getValue() = version + or + name = entry.getListItem().getValue().getValue().trim() and + version = "\"*\"" + ) + } + + /** Gets the database scheme of this qlpack */ + File getDBScheme() { + result.getAbsolutePath() = + file.getParentContainer().getAbsolutePath() + "/" + this.getProperty("dbscheme") + } + + pragma[noinline] + Container getAFileInPack() { + result.getParentContainer() = file.getParentContainer() + or + result = this.getAFileInPack().(Folder).getAChildContainer() + } + + /** + * Gets a QLPack that this QLPack depends on. + */ + QLPack getADependency() { + exists(string name | this.hasDependency(name, _) | + result.getName().replaceAll("-", "/") = name.replaceAll("-", "/") + ) + } + + Location getLocation() { + // hacky, just pick the first node in the file. + result = + min(YAMLNode entry, Location l, File f | + entry.getLocation().getFile() = file and + f = file and + l = entry.getLocation() + | + entry order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine() + ).getLocation() + } + } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/AstNodes.qll b/ql/ql/src/codeql_ql/ast/internal/AstNodes.qll new file mode 100644 index 000000000000..c3d22be6346f --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/AstNodes.qll @@ -0,0 +1,226 @@ +import codeql_ql.ast.Ast as AST +import TreeSitter +private import Builtins + +cached +newtype TAstNode = + TTopLevel(QL::Ql file) or + TQLDoc(QL::Qldoc qldoc) or + TClasslessPredicate(QL::ClasslessPredicate pred) or + TVarDecl(QL::VarDecl decl) or + TFieldDecl(QL::Field field) 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 + 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 + 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(QL::QualifiedExpr expr) { + expr.getChild(_).(QL::QualifiedRhs).getChild(_) instanceof QL::TypeExpr + } or + 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(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) + } + +class TFormula = + TDisjunction or TConjunction or TComparisonFormula or TQuantifier or TNegation or TIfFormula or + TImplication or TInstanceOf or TCall or THigherOrderFormula or TInFormula; + +class TBinOpExpr = TAddSubExpr or TMulDivModExpr; + +class TAggregate = TFullAggregate or TExprAggregate; + +class TExpr = + TBinOpExpr or TLiteral or TAggregate or TIdentifier or TInlineCast or TCall or TUnaryExpr or + TExprAnnotation or TDontCare or TRange or TSet or TAsExpr or TSuper; + +class TCall = TPredicateCall or TMemberCall or TNoneCall or TAnyCall; + +class TModuleRef = TImport or TModuleExpr; + +class TYAMLNode = TYamlCommemt or TYamlEntry or TYamlKey or TYamlListitem or TYamlValue; + +private QL::AstNode toQLFormula(AST::AstNode n) { + n = TConjunction(result) or + n = TDisjunction(result) or + n = TComparisonFormula(result) or + n = TQuantifier(result) or + n = TFullAggregate(result) or + n = TIdentifier(result) or + n = TNegation(result) or + n = TIfFormula(result) or + n = TImplication(result) or + n = TInstanceOf(result) or + n = THigherOrderFormula(result) or + n = TInFormula(result) +} + +private QL::AstNode toQLExpr(AST::AstNode n) { + n = TAddSubExpr(result) or + n = TMulDivModExpr(result) or + n = TRange(result) or + n = TSet(result) or + n = TExprAnnotation(result) or + n = TLiteral(result) or + n = TFullAggregate(result) or + n = TExprAggregate(result) or + n = TIdentifier(result) or + n = TUnaryExpr(result) or + n = TDontCare(result) +} + +private QL::AstNode toGenerateYAML(AST::AstNode n) { + n = TYamlCommemt(result) or + n = TYamlEntry(result) or + n = TYamlKey(result) or + n = TYamlListitem(result) or + n = TYamlValue(result) +} + +/** + * Gets the underlying TreeSitter entity for a given AST node. + */ +cached +QL::AstNode toQL(AST::AstNode n) { + result = toQLExpr(n) + or + result = toQLFormula(n) + or + result = toGenerateYAML(n) + or + result.(QL::ParExpr).getChild() = toQL(n) + or + result = + any(QL::AsExpr ae | + not ae.getChild(1) instanceof QL::VarName and + toQL(n) = ae.getChild(0) + ) + or + n = TTopLevel(result) + or + n = TQLDoc(result) + or + n = TClasslessPredicate(result) + or + n = TVarDecl(result) + or + n = TFieldDecl(result) + or + n = TClass(result) + or + n = TCharPred(result) + or + n = TClassPredicate(result) + or + n = TDBRelation(result) + or + n = TSelect(result) + or + n = TModule(result) + or + n = TNewType(result) + or + n = TNewTypeBranch(result) + or + n = TImport(result) + or + n = TType(result) + or + n = TAsExpr(result) + or + n = TModuleExpr(result) + or + n = TPredicateExpr(result) + or + n = TPredicateCall(result) + or + n = TMemberCall(result) + or + n = TInlineCast(result) + or + n = TNoneCall(result) + or + n = TAnyCall(result) + or + n = TSuper(result) + or + n = TAnnotation(result) + or + n = TAnnotationArg(result) +} + +class TPredicate = + TCharPred or TClasslessPredicate or TClassPredicate or TDBRelation or TNewTypeBranch; + +class TPredOrBuiltin = TPredicate or TNewTypeBranch or TBuiltin; + +class TBuiltin = TBuiltinClassless or TBuiltinMember; + +class TModuleMember = TModuleDeclaration or TImport or TSelect or TQLDoc; + +class TDeclaration = TTypeDeclaration or TModuleDeclaration or TPredicate or TVarDecl; + +class TTypeDeclaration = TClass or TNewType or TNewTypeBranch; + +class TModuleDeclaration = TClasslessPredicate or TModule or TClass or TNewType; + +class TVarDef = TVarDecl or TAsExpr; + +module AstConsistency { + import ql + + query predicate nonTotalGetParent(AstNode node) { + exists(toQL(node).getParent()) and + not exists(node.getParent()) and + not node.getLocation().getStartColumn() = 1 and // startcolumn = 1 <=> top level in file <=> fine to have no parent + exists(node.toString()) and // <- there are a few parse errors in "global-data-flow-java-1.ql", this way we filter them out. + not node instanceof YAML::YAMLNode and // parents for YAML doens't work + not (node instanceof QLDoc and node.getLocation().getFile().getExtension() = "dbscheme") // qldoc in dbschemes are not hooked up + } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/Builtins.qll b/ql/ql/src/codeql_ql/ast/internal/Builtins.qll new file mode 100644 index 000000000000..1fc1b3f992d4 --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/Builtins.qll @@ -0,0 +1,96 @@ +private import codeql_ql.ast.internal.Type + +predicate isBuiltinClassless(string sig) { + sig = + [ + "predicate any()", "predicate none()", "predicate toUrl(string, int, int, int, string)", + "predicate toUrl(string, int, int, int, int, string)" + ] +} + +predicate isBuiltinClassless(string ret, string name, string args) { + exists(string sig, string re | re = "(\\w+) (\\w+)\\(([\\w, ]*)\\)" | + isBuiltinClassless(sig) and + ret = sig.regexpCapture(re, 1) and + name = sig.regexpCapture(re, 2) and + args = sig.regexpCapture(re, 3) + ) +} + +predicate isBuiltinMember(string sig) { + sig = + [ + "boolean boolean.booleanAnd(boolean)", "boolean boolean.booleanOr(boolean)", + "boolean boolean.booleanXor(boolean)", "boolean boolean.booleanNot()", + "string boolean.toString()", "float float.abs()", "float float.acos()", "float float.atan()", + "int float.ceil()", "float float.copySign(float)", "float float.cos()", "float float.cosh()", + "float float.exp()", "int float.floor()", "float float.log()", "float float.log(float)", + "float float.log2()", "float float.log10()", "float float.maximum(float)", + "float float.minimum(float)", "float float.nextAfter(float)", "float float.nextDown()", + "float float.nextUp()", "float float.pow(float)", "float float.signum()", "float float.sin()", + "float float.sinh()", "float float.sqrt()", "float float.tan()", "float float.tanh()", + "string float.toString()", "float float.ulp()", "int int.abs()", "int int.bitAnd(int)", + "int int.bitOr(int)", "int int.bitNot()", "int int.bitXor(int)", "int int.bitShiftLeft(int)", + "int int.bitShiftRight(int)", "int int.bitShiftRightSigned(int)", "int int.gcd(int)", + "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 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()", + "float string.toFloat()", "int string.toInt()", "string string.toString()", + "string string.toLowerCase()", "string string.toUpperCase()", "string string.trim()", + "int date.daysTo(date)", "int date.getDay()", "int date.getHours()", "int date.getMinutes()", + "int date.getMonth()", "int date.getSeconds()", "int date.getYear()", + "string date.toString()", "string date.toISO()", "string int.toUnicode()", + "string any.getAQlClass()" + /* getAQlClass is special , see Predicate.qll*/ + ] +} + +predicate isBuiltinMember(string qual, string ret, string name, string args) { + exists(string sig, string re | re = "(\\w+) (\\w+)\\.(\\w+)\\(([\\w, ]*)\\)" | + isBuiltinMember(sig) and + ret = sig.regexpCapture(re, 1) and + qual = sig.regexpCapture(re, 2) and + name = sig.regexpCapture(re, 3) and + args = sig.regexpCapture(re, 4) + ) +} + +module BuiltinsConsistency { + query predicate noBuildinParse(string sig) { + isBuiltinMember(sig) and + not exists(sig.regexpCapture("(\\w+) (\\w+)\\.(\\w+)\\(([\\w, ]*)\\)", _)) + } + + query 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() } + +/** The primitive 'string' class. */ +class StringClass extends PrimitiveType { + StringClass() { this.getName() = "string" } +} + +/** The primitive 'int' class. */ +class IntClass extends PrimitiveType { + IntClass() { this.getName() = "int" } +} + +/** The primitive 'float' class. */ +class FloatClass extends PrimitiveType { + FloatClass() { this.getName() = "float" } +} + +/** The primitive 'boolean' class. */ +class BooleanClass extends PrimitiveType { + BooleanClass() { this.getName() = "boolean" } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/Module.qll b/ql/ql/src/codeql_ql/ast/internal/Module.qll new file mode 100644 index 000000000000..acf47a86017a --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/Module.qll @@ -0,0 +1,297 @@ +import ql +private import codeql_ql.ast.internal.AstNodes as AstNodes +private import codeql_ql.ast.internal.TreeSitter + +private class ContainerOrModule extends TContainerOrModule { + string getName() { none() } + + ContainerOrModule getEnclosing() { none() } + + string toString() { none() } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } +} + +private class TFileOrModule = TFile or TModule; + +/** A file or a module. */ +class FileOrModule extends TFileOrModule, ContainerOrModule { + abstract File getFile(); +} + +private class File_ extends FileOrModule, TFile { + File f; + + File_() { this = TFile(f) } + + override ContainerOrModule getEnclosing() { result = TFolder(f.getParentContainer()) } + + override string getName() { result = f.getStem().replaceAll(" ", "_") } + + override string toString() { result = f.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = f.getAbsolutePath() and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + + override File getFile() { result = f } +} + +private class Folder_ extends ContainerOrModule, TFolder { + Folder f; + + Folder_() { this = TFolder(f) } + + override ContainerOrModule getEnclosing() { + result = TFolder(f.getParentContainer()) and + // if this the the root, then we stop. + not exists(f.getFile("qlpack.yml")) + } + + override string getName() { result = f.getStem().replaceAll(" ", "_") } + + override string toString() { result = f.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = f.getAbsolutePath() and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + + /** + * Gets the folder that this IPA type represents. + */ + Folder getFolder() { result = f } +} + +class Module_ extends FileOrModule, TModule { + Module m; + + Module_() { this = TModule(m) } + + override ContainerOrModule getEnclosing() { result = getEnclosingModule(m) } + + override string getName() { result = m.getName() } + + override string toString() { result = m.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + m.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + override File getFile() { result = m.getLocation().getFile() } +} + +private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) { + not m = TFile(any(File f | f.getExtension() = "ql")) and + exists(string q | q = imp.getQualifiedName(i) | + i = 0 and + ( + exists(Container c, Container parent | + // should ideally look at `qlpack.yml` files + parent = imp.getLocation().getFile().getParentContainer+() and + exists(YAML::QLPack pack | + pack.getFile().getParentContainer() = parent and + c.getParentContainer() = pack.getADependency*().getFile().getParentContainer() + ) and + q = m.getName() + | + m = TFile(c) + or + m = TFolder(c) + ) + or + q = imp.getQualifiedName(i) and + exists(ContainerOrModule container | container = getEnclosingModule(imp).getEnclosing+() | + definesModule(container, q, m, _) and + ( + exists(container.(Folder_).getFolder().getFile("qlpack.yml")) or + container.(Folder_).getFolder() = imp.getLocation().getFile().getParentContainer() or + not container instanceof Folder_ + ) + ) + or + definesModule(getEnclosingModule(imp), q, m, _) + ) + or + exists(Folder_ mid | + resolveQualifiedName(imp, mid, i - 1) and + m.getEnclosing() = mid and + q = m.getName() + ) + ) +} + +private predicate resolveSelectionName(Import imp, ContainerOrModule m, int i) { + (m.(File_).getFile().getExtension() = "qll" or not m instanceof File_) and + exists(int last | + resolveQualifiedName(imp, m, last) and + last = count(int j | exists(imp.getQualifiedName(j))) - 1 + ) and + not m instanceof Folder_ and + i = -1 + or + exists(ContainerOrModule mid | + resolveSelectionName(imp, mid, i - 1) and + definesModule(mid, imp.getSelectionName(i), m, true) + ) +} + +cached +private module Cached { + private AstNode parent(AstNode n) { + result = n.getParent() and + not n instanceof Module + } + + private Module getEnclosingModule0(AstNode n) { result = parent*(n.getParent()) } + + cached + ContainerOrModule getEnclosingModule(AstNode n) { + result = TModule(getEnclosingModule0(n)) + or + not exists(getEnclosingModule0(n)) and + result = TFile(n.getLocation().getFile()) + } + + cached + module NewType { + cached + newtype TContainerOrModule = + TFile(File f) or + TFolder(Folder f) or + TModule(Module m) + } + + /** Holds if import statement `imp` resolves to `m`. */ + cached + predicate resolve(Import imp, FileOrModule m) { + exists(int last | + resolveSelectionName(imp, m, last) and + last = count(int j | exists(imp.getSelectionName(j))) - 1 + ) + } + + /** Holds if module expression `me` resolves to `m`. */ + cached + predicate resolveModuleExpr(ModuleExpr me, FileOrModule m) { + not m = TFile(any(File f | f.getExtension() = "ql")) and + not exists(me.getQualifier()) and + exists(ContainerOrModule enclosing, string name | resolveModuleExprHelper(me, enclosing, name) | + definesModule(enclosing.getEnclosing*(), name, m, _) + ) + or + exists(FileOrModule mid | + resolveModuleExpr(me.getQualifier(), mid) and + definesModule(mid, me.getName(), m, true) + ) + } + + pragma[noinline] + private predicate resolveModuleExprHelper(ModuleExpr me, ContainerOrModule enclosing, string name) { + enclosing = getEnclosingModule(me) and + name = me.getName() + } +} + +import Cached +private import NewType + +boolean getPublicBool(AstNode n) { + if n.(ModuleMember).isPrivate() or n.(NewTypeBranch).getNewType().isPrivate() + then result = false + else result = true +} + +/** + * Holds if `container` defines module `m` with name `name`. + * + * `m` may be defined either directly or through `import`s. + */ +private predicate definesModule( + ContainerOrModule container, string name, ContainerOrModule m, boolean public +) { + container = m.getEnclosing() and + name = m.getName() and + ( + (m instanceof File_ or m instanceof Folder_) and + public = true + or + m = TModule(any(Module mod | public = getPublicBool(mod))) + ) + or + // import X + exists(Import imp, ContainerOrModule m0 | + container = getEnclosingModule(imp) and + resolve(imp, m0) and + not exists(imp.importedAs()) and + definesModule(m0, name, m, true) and + public = getPublicBool(imp) + ) + or + // import X as Y + exists(Import imp | + container = getEnclosingModule(imp) and + name = imp.importedAs() and + resolve(imp, m) and + public = getPublicBool(imp) + ) + or + // module X = Y + exists(Module alias | + container = getEnclosingModule(alias) and + name = alias.getName() and + resolveModuleExpr(alias.getAlias(), m) and + public = getPublicBool(alias) + ) +} + +module ModConsistency { + query predicate noResolve(Import imp) { + not resolve(imp, _) and + not imp.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate noResolveModuleExpr(ModuleExpr me) { + not resolveModuleExpr(me, _) and + not me.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate multipleResolve(Import imp, int c, ContainerOrModule m) { + c = strictcount(ContainerOrModule m0 | resolve(imp, m0)) and + c > 1 and + resolve(imp, m) and + not imp.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate multipleResolveModuleExpr(ModuleExpr me, int c, ContainerOrModule m) { + c = strictcount(ContainerOrModule m0 | resolveModuleExpr(me, m0)) and + c > 1 and + resolveModuleExpr(me, m) + } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/Predicate.qll b/ql/ql/src/codeql_ql/ast/internal/Predicate.qll new file mode 100644 index 000000000000..3fa505e462fb --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/Predicate.qll @@ -0,0 +1,174 @@ +import ql +private import Builtins +private import codeql_ql.ast.internal.Module +private import codeql_ql.ast.internal.AstNodes + +private predicate definesPredicate( + FileOrModule m, string name, int arity, Predicate p, boolean public +) { + ( + p instanceof NewTypeBranch or + p instanceof ClasslessPredicate + ) and + ( + m = getEnclosingModule(p) and + name = p.getName() and + public = getPublicBool(p) and + arity = p.getArity() + or + // import X + exists(Import imp, FileOrModule m0 | + m = getEnclosingModule(imp) and + m0 = imp.getResolvedModule() and + not exists(imp.importedAs()) and + definesPredicate(m0, name, arity, p, true) and + public = getPublicBool(imp) + ) + or + // predicate X = Y + exists(ClasslessPredicate alias | + m = getEnclosingModule(alias) and + name = alias.getName() and + resolvePredicateExpr(alias.getAlias(), p) and + public = getPublicBool(alias) and + arity = alias.getArity() + ) + ) +} + +cached +private module Cached { + cached + predicate resolvePredicateExpr(PredicateExpr pe, Predicate p) { + exists(FileOrModule m, boolean public | + not exists(pe.getQualifier()) and + m = getEnclosingModule(pe).getEnclosing*() and + public = [false, true] + or + m = pe.getQualifier().getResolvedModule() and + public = true + | + definesPredicate(m, pe.getName(), p.getArity(), p, public) + ) + } + + private predicate resolvePredicateCall(PredicateCall pc, PredicateOrBuiltin p) { + exists(Class c, ClassType t | + c = pc.getParent*() and + t = c.getType() and + p = t.getClassPredicate(pc.getPredicateName(), pc.getNumberOfArguments()) + ) + or + exists(FileOrModule m, boolean public | + not exists(pc.getQualifier()) and + m = getEnclosingModule(pc).getEnclosing*() and + public = [false, true] + or + m = pc.getQualifier().getResolvedModule() and + public = true + | + definesPredicate(m, pc.getPredicateName(), pc.getNumberOfArguments(), p, public) + ) + } + + private predicate resolveMemberCall(MemberCall mc, PredicateOrBuiltin p) { + exists(Type t | + t = mc.getBase().getType() and + p = t.getClassPredicate(mc.getMemberName(), mc.getNumberOfArguments()) + ) + or + // super calls + exists(Super sup, ClassType type, Type supertype | + mc.getBase() = sup and + sup.getEnclosingPredicate().getParent().(Class).getType() = type and + supertype in [type.getASuperType(), type.getAnInstanceofType()] and + p = supertype.getClassPredicate(mc.getMemberName(), mc.getNumberOfArguments()) + ) + } + + pragma[noinline] + private predicate candidate(Relation rel, PredicateCall pc) { + rel.getName() = pc.getPredicateName() + } + + private predicate resolveDBRelation(PredicateCall pc, Predicate p) { + exists(Relation rel | p = rel | + candidate(rel, pc) and + rel.getArity() = pc.getNumberOfArguments() and + ( + exists(YAML::QLPack libPack, YAML::QLPack qlPack | + rel.getLocation().getFile() = libPack.getDBScheme() and + qlPack.getADependency*() = libPack and + qlPack.getAFileInPack() = pc.getLocation().getFile() + ) + or + // upgrade scripts don't have a qlpack + rel.getLocation().getFile().getParentContainer() = + pc.getLocation().getFile().getParentContainer() + ) + ) + } + + private predicate resolveBuildinPredicateCall(PredicateCall call, BuiltinClassless pred) { + call.getNumberOfArguments() = pred.getArity() and + call.getPredicateName() = pred.getName() + } + + cached + predicate resolveCall(Call c, PredicateOrBuiltin p) { + resolvePredicateCall(c, p) + or + not resolvePredicateCall(c, _) and + resolveBuildinPredicateCall(c, p) + or + resolveMemberCall(c, p) + or + not resolvePredicateCall(c, _) and + resolveDBRelation(c, p) + or + // getAQlClass() is special + c.(MemberCall).getMemberName() = "getAQlClass" and + p.(BuiltinMember).getName() = "getAQlClass" + } +} + +import Cached + +module PredConsistency { + query predicate noResolvePredicateExpr(PredicateExpr pe) { + not resolvePredicateExpr(pe, _) and + not pe.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate noResolveCall(Call c) { + not resolveCall(c, _) and + not c instanceof NoneCall and + not c instanceof AnyCall and + not c.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate multipleResolvePredicateExpr(PredicateExpr pe, int c, ClasslessPredicate p) { + c = strictcount(ClasslessPredicate p0 | resolvePredicateExpr(pe, p0)) and + c > 1 and + resolvePredicateExpr(pe, p) + } + + query predicate multipleResolveCall(Call call, int c, PredicateOrBuiltin p) { + c = + strictcount(PredicateOrBuiltin p0 | + resolveCall(call, p0) and + // aliases are expected to resolve to multiple. + not exists(p0.(ClasslessPredicate).getAlias()) and + // overridden predicates may have multiple targets + not p0.(ClassPredicate).isOverride() + ) and + c > 1 and + resolveCall(call, p) + } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/TreeSitter.qll b/ql/ql/src/codeql_ql/ast/internal/TreeSitter.qll new file mode 100644 index 000000000000..cfef117f2691 --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/TreeSitter.qll @@ -0,0 +1,1780 @@ +/* + * CodeQL library for QL + * Automatically generated from the tree-sitter grammar; do not edit + */ + +private import codeql.files.FileSystem +private import codeql.Locations + +module QL { + /** The base class for all AST nodes */ + class AstNode extends @ql_ast_node { + /** Gets a string representation of this element. */ + string toString() { result = this.getAPrimaryQlClass() } + + /** Gets the location of this element. */ + Location getLocation() { none() } + + /** Gets the parent of this element. */ + AstNode getParent() { ql_ast_node_parent(this, result, _) } + + /** Gets the index of this node among the children of its parent. */ + 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 @ql_token, AstNode { + /** Gets the value of this token. */ + string getValue() { ql_tokeninfo(this, _, result, _) } + + /** Gets the location of this token. */ + override Location getLocation() { ql_tokeninfo(this, _, _, result) } + + /** Gets a string representation of this element. */ + 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 @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 @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() { ql_add_expr_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_add_expr_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_add_expr_def(this, _, result, _, _) } + + /** Gets the child of this node. */ + Addop getChild() { ql_add_expr_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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 @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() { ql_aggregate_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_aggregate_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_aggregate_child(this, _, result) } + } + + /** A class representing `annotArg` nodes. */ + 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() { ql_annot_arg_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_annot_arg_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_annot_arg_def(this, result, _) } + } + + /** A class representing `annotName` tokens. */ + 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 @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() { ql_annotation_def(this, _, result) } + + /** Gets the node corresponding to the field `args`. */ + AstNode getArgs(int i) { ql_annotation_args(this, i, result) } + + /** Gets the node corresponding to the field `name`. */ + AnnotName getName() { ql_annotation_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_annotation_args(this, _, result) or ql_annotation_def(this, result, _) + } + } + + /** A class representing `aritylessPredicateExpr` nodes. */ + 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() { ql_arityless_predicate_expr_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + LiteralId getName() { ql_arityless_predicate_expr_def(this, result, _) } + + /** Gets the child of this node. */ + ModuleExpr getChild() { ql_arityless_predicate_expr_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_arityless_predicate_expr_def(this, result, _) or + ql_arityless_predicate_expr_child(this, result) + } + } + + /** A class representing `asExpr` nodes. */ + 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() { ql_as_expr_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_as_expr_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_as_expr_child(this, _, result) } + } + + /** A class representing `asExprs` nodes. */ + 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() { ql_as_exprs_def(this, result) } + + /** Gets the `i`th child of this node. */ + AsExpr getChild(int i) { ql_as_exprs_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_as_exprs_child(this, _, result) } + } + + /** A class representing `block_comment` tokens. */ + 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 @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() { ql_body_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_body_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_body_def(this, result, _) } + } + + /** A class representing `bool` nodes. */ + 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() { ql_bool_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_bool_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_bool_def(this, result, _) } + } + + /** A class representing `call_body` nodes. */ + 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() { ql_call_body_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_call_body_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_call_body_child(this, _, result) } + } + + /** A class representing `call_or_unqual_agg_expr` nodes. */ + 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() { ql_call_or_unqual_agg_expr_def(this, result) } + + /** Gets the `i`th child of this node. */ + 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() { ql_call_or_unqual_agg_expr_child(this, _, result) } + } + + /** A class representing `charpred` nodes. */ + 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() { ql_charpred_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + AstNode getBody() { ql_charpred_def(this, result, _, _) } + + /** Gets the child of this node. */ + ClassName getChild() { ql_charpred_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_charpred_def(this, result, _, _) or ql_charpred_def(this, _, result, _) + } + } + + /** A class representing `classMember` nodes. */ + 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() { ql_class_member_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_class_member_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_class_member_child(this, _, result) } + } + + /** A class representing `className` tokens. */ + 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 @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() { ql_classless_predicate_def(this, _, _, result) } + + /** Gets the node corresponding to the field `name`. */ + PredicateName getName() { ql_classless_predicate_def(this, result, _, _) } + + /** Gets the node corresponding to the field `returnType`. */ + AstNode getReturnType() { ql_classless_predicate_def(this, _, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_classless_predicate_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_comp_term_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_comp_term_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_comp_term_def(this, _, result, _, _) } + + /** Gets the child of this node. */ + Compop getChild() { ql_comp_term_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_conjunction_def(this, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_conjunction_def(this, result, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_conjunction_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_conjunction_def(this, result, _, _) or ql_conjunction_def(this, _, result, _) + } + } + + /** A class representing `dataclass` nodes. */ + 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() { ql_dataclass_def(this, _, result) } + + /** Gets the node corresponding to the field `extends`. */ + AstNode getExtends(int i) { ql_dataclass_extends(this, i, result) } + + /** Gets the node corresponding to the field `instanceof`. */ + AstNode getInstanceof(int i) { ql_dataclass_instanceof(this, i, result) } + + /** Gets the node corresponding to the field `name`. */ + ClassName getName() { ql_dataclass_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_dataclass_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_datatype_def(this, _, _, result) } + + /** Gets the node corresponding to the field `name`. */ + ClassName getName() { ql_datatype_def(this, result, _, _) } + + /** Gets the child of this node. */ + DatatypeBranches getChild() { ql_datatype_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_datatype_def(this, result, _, _) or ql_datatype_def(this, _, result, _) + } + } + + /** A class representing `datatypeBranch` nodes. */ + 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() { ql_datatype_branch_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + ClassName getName() { ql_datatype_branch_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_datatype_branch_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_datatype_branch_def(this, result, _) or ql_datatype_branch_child(this, _, result) + } + } + + /** A class representing `datatypeBranches` nodes. */ + 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() { ql_datatype_branches_def(this, result) } + + /** Gets the `i`th child of this node. */ + DatatypeBranch getChild(int i) { ql_datatype_branches_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_datatype_branches_child(this, _, result) } + } + + /** A class representing `db_annotation` nodes. */ + 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() { ql_db_annotation_def(this, result) } + + /** Gets the node corresponding to the field `argsAnnotation`. */ + DbArgsAnnotation getArgsAnnotation() { ql_db_annotation_args_annotation(this, result) } + + /** Gets the node corresponding to the field `simpleAnnotation`. */ + AnnotName getSimpleAnnotation() { ql_db_annotation_simple_annotation(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_db_annotation_args_annotation(this, result) or + ql_db_annotation_simple_annotation(this, result) + } + } + + /** A class representing `db_argsAnnotation` nodes. */ + 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() { ql_db_args_annotation_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + AnnotName getName() { ql_db_args_annotation_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + SimpleId getChild(int i) { ql_db_args_annotation_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_db_args_annotation_def(this, result, _) or ql_db_args_annotation_child(this, _, result) + } + } + + /** A class representing `db_boolean` tokens. */ + 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 @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() { ql_db_branch_def(this, result) } + + /** Gets the node corresponding to the field `qldoc`. */ + Qldoc getQldoc() { ql_db_branch_qldoc(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_db_branch_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_db_branch_qldoc(this, result) or ql_db_branch_child(this, _, result) + } + } + + /** A class representing `db_case` tokens. */ + 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 @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() { ql_db_case_decl_def(this, _, _, result) } + + /** Gets the node corresponding to the field `base`. */ + Dbtype getBase() { ql_db_case_decl_def(this, result, _, _) } + + /** Gets the node corresponding to the field `discriminator`. */ + SimpleId getDiscriminator() { ql_db_case_decl_def(this, _, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_db_case_decl_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_db_col_type_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_db_col_type_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_db_col_type_def(this, result, _) } + } + + /** A class representing `db_column` nodes. */ + 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() { ql_db_column_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `colName`. */ + SimpleId getColName() { ql_db_column_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `colType`. */ + DbColType getColType() { ql_db_column_def(this, _, result, _, _) } + + /** Gets the node corresponding to the field `isRef`. */ + DbRef getIsRef() { ql_db_column_is_ref(this, result) } + + /** Gets the node corresponding to the field `isUnique`. */ + DbUnique getIsUnique() { ql_db_column_is_unique(this, result) } + + /** Gets the node corresponding to the field `qldoc`. */ + Qldoc getQldoc() { ql_db_column_qldoc(this, result) } + + /** Gets the node corresponding to the field `reprType`. */ + DbReprType getReprType() { ql_db_column_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_db_entry_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_db_entry_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_db_entry_def(this, result, _) } + } + + /** A class representing `db_float` tokens. */ + 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 @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 @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 @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() { ql_db_repr_type_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_db_repr_type_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_db_repr_type_child(this, _, result) } + } + + /** A class representing `db_string` tokens. */ + 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 @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() { ql_db_table_def(this, _, result) } + + /** Gets the node corresponding to the field `tableName`. */ + DbTableName getTableName() { ql_db_table_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_db_table_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_db_table_def(this, result, _) or ql_db_table_child(this, _, result) + } + } + + /** A class representing `db_tableName` nodes. */ + 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() { ql_db_table_name_def(this, _, result) } + + /** Gets the child of this node. */ + SimpleId getChild() { ql_db_table_name_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_db_table_name_def(this, result, _) } + } + + /** A class representing `db_unionDecl` nodes. */ + 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() { ql_db_union_decl_def(this, _, result) } + + /** Gets the node corresponding to the field `base`. */ + Dbtype getBase() { ql_db_union_decl_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + Dbtype getChild(int i) { ql_db_union_decl_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_db_union_decl_def(this, result, _) or ql_db_union_decl_child(this, _, result) + } + } + + /** A class representing `db_unique` tokens. */ + 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 @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 @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 @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 @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() { ql_disjunction_def(this, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_disjunction_def(this, result, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_disjunction_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_disjunction_def(this, result, _, _) or ql_disjunction_def(this, _, result, _) + } + } + + /** A class representing `empty` tokens. */ + 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 @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() { ql_expr_aggregate_body_def(this, _, result) } + + /** Gets the node corresponding to the field `asExprs`. */ + AsExprs getAsExprs() { ql_expr_aggregate_body_def(this, result, _) } + + /** Gets the node corresponding to the field `orderBys`. */ + OrderBys getOrderBys() { ql_expr_aggregate_body_order_bys(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_expr_annotation_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `annot_arg`. */ + AnnotName getAnnotArg() { ql_expr_annotation_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `name`. */ + AnnotName getName() { ql_expr_annotation_def(this, _, result, _, _) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_expr_annotation_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_field_def(this, _, result) } + + /** Gets the child of this node. */ + VarDecl getChild() { ql_field_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_field_def(this, result, _) } + } + + /** A class representing `float` tokens. */ + 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 @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() { ql_full_aggregate_body_def(this, result) } + + /** Gets the node corresponding to the field `asExprs`. */ + AsExprs getAsExprs() { ql_full_aggregate_body_as_exprs(this, result) } + + /** Gets the node corresponding to the field `guard`. */ + AstNode getGuard() { ql_full_aggregate_body_guard(this, result) } + + /** Gets the node corresponding to the field `orderBys`. */ + OrderBys getOrderBys() { ql_full_aggregate_body_order_bys(this, result) } + + /** Gets the `i`th child of this node. */ + VarDecl getChild(int i) { ql_full_aggregate_body_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_higher_order_term_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + LiteralId getName() { ql_higher_order_term_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_higher_order_term_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_higher_order_term_def(this, result, _) or ql_higher_order_term_child(this, _, result) + } + } + + /** A class representing `if_term` nodes. */ + 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() { ql_if_term_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `cond`. */ + AstNode getCond() { ql_if_term_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `first`. */ + AstNode getFirst() { ql_if_term_def(this, _, result, _, _) } + + /** Gets the node corresponding to the field `second`. */ + AstNode getSecond() { ql_if_term_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_implication_def(this, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_implication_def(this, result, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_implication_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_implication_def(this, result, _, _) or ql_implication_def(this, _, result, _) + } + } + + /** A class representing `importDirective` nodes. */ + 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() { ql_import_directive_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_import_directive_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_import_directive_child(this, _, result) } + } + + /** A class representing `importModuleExpr` nodes. */ + 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() { ql_import_module_expr_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + SimpleId getName(int i) { ql_import_module_expr_name(this, i, result) } + + /** Gets the child of this node. */ + QualModuleExpr getChild() { ql_import_module_expr_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_import_module_expr_name(this, _, result) or ql_import_module_expr_def(this, result, _) + } + } + + /** A class representing `in_expr` nodes. */ + 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() { ql_in_expr_def(this, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_in_expr_def(this, result, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_in_expr_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_in_expr_def(this, result, _, _) or ql_in_expr_def(this, _, result, _) + } + } + + /** A class representing `instance_of` nodes. */ + 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() { ql_instance_of_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_instance_of_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_instance_of_child(this, _, result) } + } + + /** A class representing `integer` tokens. */ + 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 @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 @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() { ql_literal_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_literal_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_literal_def(this, result, _) } + } + + /** A class representing `literalId` tokens. */ + 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 @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() { ql_member_predicate_def(this, _, _, result) } + + /** Gets the node corresponding to the field `name`. */ + PredicateName getName() { ql_member_predicate_def(this, result, _, _) } + + /** Gets the node corresponding to the field `returnType`. */ + AstNode getReturnType() { ql_member_predicate_def(this, _, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_member_predicate_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_module_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + ModuleName getName() { ql_module_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_module_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_module_def(this, result, _) or ql_module_child(this, _, result) + } + } + + /** A class representing `moduleAliasBody` nodes. */ + 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() { ql_module_alias_body_def(this, _, result) } + + /** Gets the child of this node. */ + ModuleExpr getChild() { ql_module_alias_body_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_module_alias_body_def(this, result, _) } + } + + /** A class representing `moduleExpr` nodes. */ + 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() { ql_module_expr_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + SimpleId getName() { ql_module_expr_name(this, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_module_expr_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_module_expr_name(this, result) or ql_module_expr_def(this, result, _) + } + } + + /** A class representing `moduleMember` nodes. */ + 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() { ql_module_member_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_module_member_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_module_member_child(this, _, result) } + } + + /** A class representing `moduleName` nodes. */ + 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() { ql_module_name_def(this, _, result) } + + /** Gets the child of this node. */ + SimpleId getChild() { ql_module_name_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_module_name_def(this, result, _) } + } + + /** A class representing `mul_expr` nodes. */ + 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() { ql_mul_expr_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ql_mul_expr_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ql_mul_expr_def(this, _, result, _, _) } + + /** Gets the child of this node. */ + Mulop getChild() { ql_mul_expr_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_negation_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_negation_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_negation_def(this, result, _) } + } + + /** A class representing `orderBy` nodes. */ + 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() { ql_order_by_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_order_by_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_order_by_child(this, _, result) } + } + + /** A class representing `orderBys` nodes. */ + 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() { ql_order_bys_def(this, result) } + + /** Gets the `i`th child of this node. */ + OrderBy getChild(int i) { ql_order_bys_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_order_bys_child(this, _, result) } + } + + /** A class representing `par_expr` nodes. */ + 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() { ql_par_expr_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_par_expr_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_par_expr_def(this, result, _) } + } + + /** A class representing `predicate` tokens. */ + 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 @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() { ql_predicate_alias_body_def(this, _, result) } + + /** Gets the child of this node. */ + PredicateExpr getChild() { ql_predicate_alias_body_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_predicate_alias_body_def(this, result, _) } + } + + /** A class representing `predicateExpr` nodes. */ + 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() { ql_predicate_expr_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_predicate_expr_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_predicate_expr_child(this, _, result) } + } + + /** A class representing `predicateName` tokens. */ + 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 @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() { ql_prefix_cast_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_prefix_cast_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_prefix_cast_child(this, _, result) } + } + + /** A class representing `primitiveType` tokens. */ + 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_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_ql_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_ql_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_ql_child(this, _, result) } + } + + /** A class representing `qldoc` tokens. */ + 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 @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() { ql_qual_module_expr_def(this, result) } + + /** Gets the node corresponding to the field `name`. */ + SimpleId getName(int i) { ql_qual_module_expr_name(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_qual_module_expr_name(this, _, result) } + } + + /** A class representing `qualifiedRhs` nodes. */ + 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() { ql_qualified_rhs_def(this, result) } + + /** Gets the node corresponding to the field `name`. */ + PredicateName getName() { ql_qualified_rhs_name(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_qualified_rhs_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_qualified_rhs_name(this, result) or ql_qualified_rhs_child(this, _, result) + } + } + + /** A class representing `qualified_expr` nodes. */ + 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() { ql_qualified_expr_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_qualified_expr_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_qualified_expr_child(this, _, result) } + } + + /** A class representing `quantified` nodes. */ + 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() { ql_quantified_def(this, result) } + + /** Gets the node corresponding to the field `expr`. */ + AstNode getExpr() { ql_quantified_expr(this, result) } + + /** Gets the node corresponding to the field `formula`. */ + AstNode getFormula() { ql_quantified_formula(this, result) } + + /** Gets the node corresponding to the field `range`. */ + AstNode getRange() { ql_quantified_range(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_quantified_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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 @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() { ql_range_def(this, _, _, result) } + + /** Gets the node corresponding to the field `lower`. */ + AstNode getLower() { ql_range_def(this, result, _, _) } + + /** Gets the node corresponding to the field `upper`. */ + AstNode getUpper() { ql_range_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_range_def(this, result, _, _) or ql_range_def(this, _, result, _) + } + } + + /** A class representing `result` tokens. */ + 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 @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() { ql_select_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_select_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_select_child(this, _, result) } + } + + /** A class representing `set_literal` nodes. */ + 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() { ql_set_literal_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_set_literal_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_set_literal_child(this, _, result) } + } + + /** A class representing `simpleId` tokens. */ + 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 @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 @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() { ql_special_call_def(this, _, result) } + + /** Gets the child of this node. */ + SpecialId getChild() { ql_special_call_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_special_call_def(this, result, _) } + } + + /** A class representing `string` tokens. */ + 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 @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 @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() { ql_super_ref_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_super_ref_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_super_ref_child(this, _, result) } + } + + /** A class representing `this` tokens. */ + 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 @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 @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() { ql_type_alias_body_def(this, _, result) } + + /** Gets the child of this node. */ + TypeExpr getChild() { ql_type_alias_body_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_type_alias_body_def(this, result, _) } + } + + /** A class representing `typeExpr` nodes. */ + 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() { ql_type_expr_def(this, result) } + + /** Gets the node corresponding to the field `name`. */ + ClassName getName() { ql_type_expr_name(this, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_type_expr_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_type_expr_name(this, result) or ql_type_expr_child(this, result) + } + } + + /** A class representing `typeUnionBody` nodes. */ + 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() { ql_type_union_body_def(this, result) } + + /** Gets the `i`th child of this node. */ + TypeExpr getChild(int i) { ql_type_union_body_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_type_union_body_child(this, _, result) } + } + + /** A class representing `unary_expr` nodes. */ + 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() { ql_unary_expr_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_unary_expr_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_unary_expr_child(this, _, result) } + } + + /** A class representing `underscore` tokens. */ + 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 @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 @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() { ql_unqual_agg_body_def(this, result) } + + /** Gets the node corresponding to the field `asExprs`. */ + AstNode getAsExprs(int i) { ql_unqual_agg_body_as_exprs(this, i, result) } + + /** Gets the node corresponding to the field `guard`. */ + AstNode getGuard() { ql_unqual_agg_body_guard(this, result) } + + /** Gets the `i`th child of this node. */ + VarDecl getChild(int i) { ql_unqual_agg_body_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + 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 @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() { ql_var_decl_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_var_decl_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_var_decl_child(this, _, result) } + } + + /** A class representing `varName` nodes. */ + 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() { ql_var_name_def(this, _, result) } + + /** Gets the child of this node. */ + SimpleId getChild() { ql_var_name_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_var_name_def(this, result, _) } + } + + /** A class representing `variable` nodes. */ + 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() { ql_variable_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_variable_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_variable_def(this, result, _) } + } + + /** A class representing `yaml_comment` nodes. */ + 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() { ql_yaml_comment_def(this, _, result) } + + /** Gets the child of this node. */ + YamlValue getChild() { ql_yaml_comment_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_yaml_comment_def(this, result, _) } + } + + /** A class representing `yaml_entry` nodes. */ + 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() { ql_yaml_entry_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ql_yaml_entry_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_yaml_entry_def(this, result, _) } + } + + /** A class representing `yaml_key` nodes. */ + 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() { ql_yaml_key_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ql_yaml_key_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_yaml_key_child(this, _, result) } + } + + /** A class representing `yaml_keyvaluepair` nodes. */ + 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() { ql_yaml_keyvaluepair_def(this, _, _, result) } + + /** Gets the node corresponding to the field `key`. */ + YamlKey getKey() { ql_yaml_keyvaluepair_def(this, result, _, _) } + + /** Gets the node corresponding to the field `value`. */ + YamlValue getValue() { ql_yaml_keyvaluepair_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ql_yaml_keyvaluepair_def(this, result, _, _) or ql_yaml_keyvaluepair_def(this, _, result, _) + } + } + + /** A class representing `yaml_listitem` nodes. */ + 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() { ql_yaml_listitem_def(this, _, result) } + + /** Gets the child of this node. */ + YamlValue getChild() { ql_yaml_listitem_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ql_yaml_listitem_def(this, result, _) } + } + + /** A class representing `yaml_value` tokens. */ + 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/ql/src/codeql_ql/ast/internal/Type.qll b/ql/ql/src/codeql_ql/ast/internal/Type.qll new file mode 100644 index 000000000000..4d88cd194d50 --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/Type.qll @@ -0,0 +1,375 @@ +import ql +private import codeql_ql.ast.internal.AstNodes as AstNodes +private import codeql_ql.ast.internal.TreeSitter +private import codeql_ql.ast.internal.Module +private import codeql_ql.ast.internal.Predicate + +cached +private newtype TType = + TClass(Class c) { isActualClass(c) } or + TNewType(NewType n) or + TNewTypeBranch(NewTypeBranch b) or + TPrimitive(string s) { primTypeName(s) } or + TUnion(Class c) { exists(c.getUnionMember()) } or + TDontCare() or + TClassChar(Class c) { isActualClass(c) } or + TClassDomain(Class c) { isActualClass(c) } or + TDatabase(string s) { exists(TypeExpr t | t.isDBType() and s = t.getClassName()) } + +private predicate primTypeName(string s) { s = ["int", "float", "string", "boolean", "date"] } + +private predicate isActualClass(Class c) { + not exists(c.getAliasType()) and + not exists(c.getUnionMember()) +} + +/** + * A type, such as `int` or `Node`. + */ +class Type extends TType { + string toString() { result = this.getName() } + + string getName() { result = "???" } + + /** + * Gets a supertype of this type. This follows the user-visible type heirarchy, + * and doesn't include internal types like the characteristic and domain types of classes. + */ + Type getASuperType() { none() } + + /** + * Gets a supertype of this type in the internal heirarchy, + * which includes the characteristic and domain types of classes. + */ + Type getAnInternalSuperType() { result = TDontCare() } + + AstNode getDeclaration() { none() } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + if exists(this.getDeclaration()) + then + this.getDeclaration() + .getLocation() + .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + else ( + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + ) + } + + pragma[nomagic] + private predicate getClassPredicate0(string name, int arity, PredicateOrBuiltin p, Type t) { + p = classPredCandidate(this, name, arity) and + t = p.getDeclaringType().getASuperType+() + } + + pragma[nomagic] + private predicate getClassPredicate1( + string name, int arity, PredicateOrBuiltin p1, PredicateOrBuiltin p2 + ) { + 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 this.getClassPredicate1(name, arity, _, result) + } +} + +class ClassType extends Type, TClass { + Class decl; + + ClassType() { this = TClass(decl) } + + override string getName() { result = decl.getName() } + + override Class getDeclaration() { result = decl } + + override Type getASuperType() { result = decl.getASuperType().getResolvedType() } + + Type getAnInstanceofType() { result = decl.getAnInstanceofType().getResolvedType() } + + override Type getAnInternalSuperType() { + result.(ClassCharType).getClassType() = this + or + result = super.getAnInternalSuperType() + } + + VarDecl getField(string name) { + result = fieldCandidate(this, name) and + not exists(VarDecl other | other = fieldCandidate(this, name) | + other.getDeclaringType().getASuperType+() = result.getDeclaringType() + ) + } +} + +private PredicateOrBuiltin declaredPred(Type ty, string name, int arity) { + result.getDeclaringType() = ty and + result.getName() = name and + result.getArity() = arity +} + +pragma[nomagic] +private PredicateOrBuiltin classPredCandidate(Type ty, string name, int arity) { + result = declaredPred(ty, name, arity) + or + not exists(declaredPred(ty, name, arity)) and + result = inherClassPredCandidate(ty, name, arity) +} + +private PredicateOrBuiltin inherClassPredCandidate(Type ty, string name, int arity) { + result = classPredCandidate(ty.getAnInternalSuperType(), name, arity) and + not result.isPrivate() +} + +predicate predOverrides(ClassPredicate sub, ClassPredicate sup) { + sup = inherClassPredCandidate(sub.getDeclaringType(), sub.getName(), sub.getArity()) +} + +private VarDecl declaredField(ClassType ty, string name) { + result = ty.getDeclaration().getAField().getVarDecl() and + result.getName() = name +} + +private VarDecl fieldCandidate(ClassType ty, string name) { + result = declaredField(ty, name) + or + not exists(declaredField(ty, name)) and + result = inherFieldCandidate(ty, name) +} + +private VarDecl inherFieldCandidate(ClassType ty, string name) { + result = fieldCandidate(ty.getASuperType(), name) and + not result.isPrivate() +} + +predicate fieldOverrides(VarDecl sub, VarDecl sup) { + sup = inherFieldCandidate(sub.getDeclaringType(), sub.getName()) +} + +class ClassCharType extends Type, TClassChar { + Class decl; + + ClassCharType() { this = TClassChar(decl) } + + override string getName() { exists(string n | n = decl.getName() | result = n + "." + n) } + + override Class getDeclaration() { result = decl } + + ClassType getClassType() { result = TClass(decl) } + + override Type getAnInternalSuperType() { + result.(ClassDomainType).getClassType() = this.getClassType() + } +} + +class ClassDomainType extends Type, TClassDomain { + Class decl; + + ClassDomainType() { this = TClassDomain(decl) } + + override string getName() { result = decl.getName() + ".extends" } + + override Class getDeclaration() { result = decl } + + ClassType getClassType() { result = TClass(decl) } + + override Type getAnInternalSuperType() { result = this.getClassType().getASuperType() } +} + +class PrimitiveType extends Type, TPrimitive { + string name; + + PrimitiveType() { this = TPrimitive(name) } + + override string getName() { result = name } + + override Type getASuperType() { name = "int" and result.(PrimitiveType).getName() = "float" } + + override Type getAnInternalSuperType() { + result = this.getASuperType() + or + result = super.getAnInternalSuperType() + } +} + +class DontCareType extends Type, TDontCare { + override string getName() { result = "_" } + + override Type getAnInternalSuperType() { none() } +} + +class NewTypeType extends Type, TNewType { + NewType decl; + + NewTypeType() { this = TNewType(decl) } + + override NewType getDeclaration() { result = decl } + + NewTypeBranchType getABranch() { result = TNewTypeBranch(decl.getABranch()) } + + override string getName() { result = decl.getName() } +} + +class NewTypeBranchType extends Type, TNewTypeBranch { + NewTypeBranch decl; + + NewTypeBranchType() { this = TNewTypeBranch(decl) } + + override NewTypeBranch getDeclaration() { result = decl } + + override string getName() { result = decl.getName() } + + override Type getASuperType() { + result = TNewType(decl.getParent()) + or + result.(UnionType).getUnionMember() = this + } + + override Type getAnInternalSuperType() { + result = this.getASuperType() + or + result = super.getAnInternalSuperType() + } +} + +class UnionType extends Type, TUnion { + Class decl; + + UnionType() { this = TUnion(decl) } + + override Class getDeclaration() { result = decl } + + override string getName() { result = decl.getName() } + + Type getUnionMember() { result = decl.getUnionMember().getResolvedType() } +} + +class DatabaseType extends Type, TDatabase { + string name; + + DatabaseType() { this = TDatabase(name) } + + override string getName() { result = name } +} + +cached +predicate resolveTypeExpr(TypeExpr te, Type t) { + if te.isDBType() + then t = TDatabase(te.getClassName()) + else + if primTypeName(te.getClassName()) + then t = TPrimitive(te.getClassName()) + else + exists(FileOrModule m, boolean public, string clName | qualifier(te, m, public, clName) | + defines(m, clName, t, public) + ) +} + +pragma[noinline] +private predicate qualifier(TypeExpr te, FileOrModule m, boolean public, string clName) { + te.getClassName() = clName and + if exists(te.getModule()) + then ( + public = true and m = te.getModule().getResolvedModule() + ) else ( + (public = true or public = false) and + m = getEnclosingModule(te).getEnclosing*() + ) +} + +pragma[nomagic] +private predicate defines(FileOrModule m, string name, Type t, boolean public) { + exists(Class ty | t = TClass(ty) | + getEnclosingModule(ty) = m and + ty.getName() = name and + public = getPublicBool(ty) + ) + or + exists(NewType ty | t = TNewType(ty) | + getEnclosingModule(ty) = m and + ty.getName() = name and + public = getPublicBool(ty) + ) + or + exists(NewTypeBranch ty | t = TNewTypeBranch(ty) | + getEnclosingModule(ty) = m and + ty.getName() = name and + public = getPublicBool(ty.getParent()) + ) + or + exists(Class ty | t = TUnion(ty) | + getEnclosingModule(ty) = m and + ty.getName() = name and + public = getPublicBool(ty) + ) + or + exists(Class ty | t = ty.getAliasType().getResolvedType() | + getEnclosingModule(ty) = m and + ty.getName() = name and + public = getPublicBool(ty) + ) + or + exists(Import im | + getEnclosingModule(im) = m and + not exists(im.importedAs()) and + public = getPublicBool(im) and + defines(im.getResolvedModule(), name, t, true) + ) +} + +module TyConsistency { + query predicate noResolve(TypeExpr te) { + not resolveTypeExpr(te, _) and + not te.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate multipleResolve(TypeExpr te, int c, Type t) { + c = strictcount(Type t0 | resolveTypeExpr(te, t0)) and + c > 1 and + resolveTypeExpr(te, t) + } + + query predicate multiplePrimitives(TypeExpr te, int c, PrimitiveType t) { + c = strictcount(PrimitiveType t0 | resolveTypeExpr(te, t0)) and + c > 1 and + resolveTypeExpr(te, t) + } + + query predicate varDefNoType(VarDef def) { + not exists(def.getType()) and + not def.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate exprNoType(Expr e) { + not exists(e.getType()) and + not exists(PredicateOrBuiltin p | + p = e.(Call).getTarget() and + not exists(p.getReturnType()) + ) and + not e instanceof Formula and + not e.getLocation() + .getFile() + .getAbsolutePath() + .regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*") + } + + query predicate multiplePrimitivesExpr(Expr e, int c, PrimitiveType t) { + c = strictcount(PrimitiveType t0 | t0 = e.getType()) and + c > 1 and + t = e.getType() + } +} diff --git a/ql/ql/src/codeql_ql/ast/internal/Variable.qll b/ql/ql/src/codeql_ql/ast/internal/Variable.qll new file mode 100644 index 000000000000..376de8a9f73c --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/internal/Variable.qll @@ -0,0 +1,96 @@ +import ql +private import codeql_ql.ast.internal.AstNodes + +private class TScope = + TClass or TAggregate or TQuantifier or TSelect or TPredicate or TNewTypeBranch; + +/** A variable scope. */ +class VariableScope extends TScope, AstNode { + /** Gets the outer scope, if any. */ + VariableScope getOuterScope() { result = scopeOf(this) } + + /** Gets a variable declared directly in this scope. */ + VarDef getADefinition(string name) { + result.getParent() = this and + name = result.getName() + } + + /** Holds if this scope contains variable `decl`, either directly or inherited. */ + predicate containsVar(VarDef decl, string name) { + name = decl.getName() and + ( + not this instanceof Class and + decl = this.getADefinition(name) + or + decl = this.(Select).getExpr(_).(AsExpr) + or + decl = this.(FullAggregate).getExpr(_).(AsExpr) + or + decl = this.(ExprAggregate).getExpr(_).(AsExpr) + or + this.getOuterScope().containsVar(decl, name) and + not exists(this.getADefinition(name)) + ) + } + + /** Holds if this scope contains field `decl`, either directly or inherited. */ + predicate containsField(VarDef decl, string name) { + name = decl.getName() and + ( + decl = this.(Class).getAField().getVarDecl() + or + this.getOuterScope().containsField(decl, name) and + not exists(this.getADefinition(name)) + or + exists(VariableScope sup | + sup = this.(Class).getASuperType().getResolvedType().(ClassType).getDeclaration() and + sup.containsField(decl, name) and + not this.(Class).getAField().getName() = name + ) + ) + } +} + +private AstNode parent(AstNode child) { + result = child.getParent() and + not child instanceof VariableScope +} + +pragma[nomagic] +VariableScope scopeOf(AstNode n) { result = parent*(n.getParent()) } + +private string getName(Identifier i) { + exists(QL::Variable v | + i = TIdentifier(v) and + result = v.getChild().(QL::VarName).getChild().getValue() + ) +} + +cached +private module Cached { + cached + predicate resolveVariable(Identifier i, VarDef decl) { scopeOf(i).containsVar(decl, getName(i)) } + + cached + predicate resolveField(Identifier i, VarDef decl) { + scopeOf(i).containsField(decl, pragma[only_bind_into](getName(i))) + } +} + +import Cached + +module VarConsistency { + query predicate multipleVarDefs(VarAccess v, VarDef decl) { + decl = v.getDeclaration() and + strictcount(v.getDeclaration()) > 1 + } + + query predicate multipleFieldDefs(FieldAccess f, VarDef decl) { + decl = f.getDeclaration() and + strictcount(f.getDeclaration()) > 1 + } + + query predicate noFieldDef(FieldAccess f) { not exists(f.getDeclaration()) } + + query predicate noVarDef(VarAccess v) { not exists(v.getDeclaration()) } +} diff --git a/ql/ql/src/codeql_ql/performance/InefficientStringComparisonQuery.qll b/ql/ql/src/codeql_ql/performance/InefficientStringComparisonQuery.qll new file mode 100644 index 000000000000..7596f2d0f544 --- /dev/null +++ b/ql/ql/src/codeql_ql/performance/InefficientStringComparisonQuery.qll @@ -0,0 +1,54 @@ +import ql +import codeql_ql.ast.internal.Predicate +import codeql_ql.ast.internal.Builtins + +class PrefixPredicate extends BuiltinPredicate { + PrefixPredicate() { this = any(StringClass sc).getClassPredicate("prefix", 1) } +} + +class SuffixPredicate extends BuiltinPredicate { + SuffixPredicate() { this = any(StringClass sc).getClassPredicate("suffix", 1) } +} + +class PrefixPredicateCall extends Call { + PrefixPredicateCall() { this.getTarget() instanceof PrefixPredicate } +} + +class SuffixPredicateCall extends Call { + SuffixPredicateCall() { this.getTarget() instanceof SuffixPredicate } +} + +class EqFormula extends ComparisonFormula { + EqFormula() { this.getOperator() = "=" } +} + +bindingset[s] +string escape(string s) { result = s.replaceAll("_", "\\\\_").replaceAll("%", "\\\\%") } + +pragma[inline] +string getMessage(FixPredicateCall call, String literal) { + call instanceof PrefixPredicateCall and + result = ".matches(\"" + escape(literal.getValue()) + "%\")" + or + call instanceof SuffixPredicateCall and + result = ".matches(\"%" + escape(literal.getValue()) + "\")" +} + +class FixPredicateCall extends Call { + FixPredicateCall() { this instanceof PrefixPredicateCall or this instanceof SuffixPredicateCall } +} + +class RegexpMatchPredicate extends BuiltinPredicate { + RegexpMatchPredicate() { this = any(StringClass sc).getClassPredicate("regexpMatch", 1) } +} + +predicate canUseMatchInsteadOfRegexpMatch(Call c, string matchesStr) { + c.getTarget() instanceof RegexpMatchPredicate and + exists(string raw | raw = c.getArgument(0).(String).getValue() | + matchesStr = "%" + raw.regexpCapture("^\\.\\*([a-zA-Z\\d\\s-]+)$", _) + or + matchesStr = raw.regexpCapture("^([a-zA-Z\\d\\s-]+)\\.\\*$", _) + "%" + or + matchesStr = "%" + raw.regexpCapture("^\\.\\*([a-zA-Z\\d\\s-]+)\\.\\*$", _) + "%" + ) +} diff --git a/ql/ql/src/codeql_ql/printAstAst.qll b/ql/ql/src/codeql_ql/printAstAst.qll new file mode 100644 index 000000000000..feb525908d21 --- /dev/null +++ b/ql/ql/src/codeql_ql/printAstAst.qll @@ -0,0 +1,101 @@ +/** + * Provides queries to pretty-print a QL abstract syntax tree as a graph. + * + * This representation is based on the user-facing AST implementation. + * + * By default, this will print the AST for all nodes in the database. To change + * this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode` + * to hold for only the AST nodes you wish to view. + */ + +import ast.Ast +private import codeql.Locations + +/** + * The query can extend this class to control which nodes are printed. + */ +class PrintAstConfiguration extends string { + PrintAstConfiguration() { this = "PrintAstConfiguration" } + + /** + * Holds if the given node should be printed. + */ + predicate shouldPrintNode(AstNode n) { any() } +} + +/** + * Gets the `i`th child of parent. + * The ordering is location based and pretty arbitary. + */ +AstNode getAstChild(PrintAstNode parent, int i) { + result = + rank[i](AstNode child, Location l | + child.getParent() = parent and + child.getLocation() = l + | + child + order by + l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() + ) +} + +/** + * A node in the output tree. + */ +class PrintAstNode extends AstNode { + PrintAstNode() { shouldPrintNode(this) } + + string getProperty(string key) { + key = "semmle.label" and + result = "[" + concat(this.getAPrimaryQlClass(), ", ") + "] " + this.toString() + or + key = "semmle.order" and + result = + any(int i | + this = + rank[i](PrintAstNode p, Location l, File f | + l = p.getLocation() and + f = l.getFile() + | + p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn() + ) + ).toString() + } + + /** + * Gets the child node that is accessed using the predicate `edgeName`. + */ + PrintAstNode getChild(string edgeName) { result = this.getAChild(edgeName) } +} + +private predicate shouldPrintNode(AstNode n) { + exists(PrintAstConfiguration config | config.shouldPrintNode(n)) +} + +/** + * Holds if `node` belongs to the output tree, and its property `key` has the + * given `value`. + */ +query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) } + +/** + * Holds if `target` is a child of `source` in the AST, and property `key` of + * the edge has the given `value`. + */ +query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) { + target = source.getChild(_) and + ( + key = "semmle.label" and + value = strictconcat(string name | source.getChild(name) = target | name, "/") + or + key = "semmle.order" and + value = target.getProperty("semmle.order") + ) +} + +/** + * Holds if property `key` of the graph has the given `value`. + */ +query predicate graphProperties(string key, string value) { + key = "semmle.graphKind" and value = "tree" +} diff --git a/ql/ql/src/codeql_ql/printAstGenerated.qll b/ql/ql/src/codeql_ql/printAstGenerated.qll new file mode 100644 index 000000000000..caee0ab809ae --- /dev/null +++ b/ql/ql/src/codeql_ql/printAstGenerated.qll @@ -0,0 +1,109 @@ +/** + * Provides queries to pretty-print a QL abstract syntax tree as a graph. + * + * This representation is based on the TreeSitter auto-generated AST. + * + * By default, this will print the AST for all nodes in the database. To change + * this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode` + * to hold for only the AST nodes you wish to view. + */ + +import ast.internal.TreeSitter::QL +private import codeql.Locations + +/** + * The query can extend this class to control which nodes are printed. + */ +class PrintAstConfiguration extends string { + PrintAstConfiguration() { this = "PrintAstConfiguration" } + + /** + * Holds if the given node should be printed. + */ + predicate shouldPrintNode(AstNode n) { + not n instanceof LineComment and + not n instanceof ReservedWord + } +} + +/** + * Gets the `i`th child of parent. + * The ordering is location based and pretty arbitary. + */ +AstNode getAstChild(PrintAstNode parent, int i) { + result = + rank[i](AstNode child, Location l | + child.getParent() = parent and + child.getLocation() = l + | + child + order by + l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString() + ) +} + +/** + * A node in the output tree. + */ +class PrintAstNode extends AstNode { + PrintAstNode() { shouldPrintNode(this) } + + string getProperty(string key) { + key = "semmle.label" and + result = "[" + concat(this.getAPrimaryQlClass(), ", ") + "] " + this.toString() + or + key = "semmle.order" and + result = + any(int i | + this = + rank[i](PrintAstNode p, Location l, File f | + l = p.getLocation() and + f = l.getFile() + | + p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn() + ) + ).toString() + } + + /** + * Gets the child node that is accessed using the predicate `edgeName`. + */ + PrintAstNode getChild(string edgeName) { + exists(int i | + result = getAstChild(this, i) and + edgeName = i.toString() + ) + } +} + +private predicate shouldPrintNode(AstNode n) { + exists(PrintAstConfiguration config | config.shouldPrintNode(n)) +} + +/** + * Holds if `node` belongs to the output tree, and its property `key` has the + * given `value`. + */ +query predicate nodes(PrintAstNode node, string key, string value) { value = node.getProperty(key) } + +/** + * Holds if `target` is a child of `source` in the AST, and property `key` of + * the edge has the given `value`. + */ +query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) { + target = source.getChild(_) and + ( + key = "semmle.label" and + value = strictconcat(string name | source.getChild(name) = target | name, "/") + or + key = "semmle.order" and + value = target.getProperty("semmle.order") + ) +} + +/** + * Holds if property `key` of the graph has the given `value`. + */ +query predicate graphProperties(string key, string value) { + key = "semmle.graphKind" and value = "tree" +} diff --git a/ql/ql/src/codeql_ql/style/ImplicitThisQuery.qll b/ql/ql/src/codeql_ql/style/ImplicitThisQuery.qll new file mode 100644 index 000000000000..9fc820f49f1c --- /dev/null +++ b/ql/ql/src/codeql_ql/style/ImplicitThisQuery.qll @@ -0,0 +1,20 @@ +import ql + +MemberCall explicitThisCallInFile(File f) { + result.getLocation().getFile() = f and + result.getBase() instanceof ThisAccess and + // Exclude `this.(Type).whatever(...)`, as some files have that as their only instance of `this`. + not result = any(InlineCast c).getBase() +} + +PredicateCall implicitThisCallInFile(File f) { + result.getLocation().getFile() = f and + exists(result.getTarget().getDeclaringType().getASuperType()) and + // Exclude `SomeModule::whatever(...)` + not exists(result.getQualifier()) +} + +PredicateCall confusingImplicitThisCall(File f) { + result = implicitThisCallInFile(f) and + exists(explicitThisCallInFile(f)) +} diff --git a/ql/ql/src/codeql_ql/style/RedundantInlineCastQuery.qll b/ql/ql/src/codeql_ql/style/RedundantInlineCastQuery.qll new file mode 100644 index 000000000000..2f349fc94a05 --- /dev/null +++ b/ql/ql/src/codeql_ql/style/RedundantInlineCastQuery.qll @@ -0,0 +1,35 @@ +import ql + +class RedundantInlineCast extends AstNode instanceof InlineCast { + Type t; + + RedundantInlineCast() { + t = unique( | | super.getType()) and + ( + // The cast is to the type the base expression already has + t = unique( | | super.getBase().getType()) + or + // The cast is to the same type as the other expression in an equality comparison + exists(ComparisonFormula comp, Expr other | comp.getOperator() = "=" | + this = comp.getAnOperand() and + other = comp.getAnOperand() and + this != other and + t = unique( | | other.getType()) and + not other instanceof InlineCast // we don't want to risk both sides being "redundant" + ) + or + exists(Call call, int i, Predicate target | + this = call.getArgument(i) and + target = unique( | | call.getTarget()) and + t = unique( | | target.getParameterType(i)) + ) + ) and + // noopt can require explicit casts + not exists(Annotation annon | annon = this.getEnclosingPredicate().getAnAnnotation() | + annon.getName() = "pragma" and + annon.getArgs(0).getValue() = "noopt" + ) + } + + TypeExpr getTypeExpr() { result = super.getTypeExpr() } +} diff --git a/ql/ql/src/codeql_ql/style/UseInstanceofExtensionQuery.qll b/ql/ql/src/codeql_ql/style/UseInstanceofExtensionQuery.qll new file mode 100644 index 000000000000..c4154612b4d2 --- /dev/null +++ b/ql/ql/src/codeql_ql/style/UseInstanceofExtensionQuery.qll @@ -0,0 +1,70 @@ +import ql + +/** + * Gets a class where the charpred has an `this instanceof type` expression. + */ +predicate instanceofThisInCharPred(Class c, Type type) { + exists(InstanceOf instanceOf | + instanceOf = c.getCharPred().getBody() + or + exists(Conjunction conj | + conj = c.getCharPred().getBody() and + instanceOf = conj.getAnOperand() + ) + | + instanceOf.getExpr() instanceof ThisAccess and + type = instanceOf.getType().getResolvedType() + ) +} + +/** + * Holds if `c` uses the casting based range pattern, which could be replaced with `instanceof type`. + */ +predicate usesCastingBasedInstanceof(Class c, Type type) { + instanceofThisInCharPred(c, type) and + // require that there is a call to the range class that matches the name of the enclosing predicate + exists(InlineCast cast, MemberCall call | + cast = getAThisCast(c, type) and + call.getBase() = cast and + cast.getEnclosingPredicate().getName() = call.getMemberName() + ) +} + +/** Gets an inline cast that cases `this` to `type` inside a class predicate for `c`. */ +InlineCast getAThisCast(Class c, Type type) { + exists(MemberCall call | + call.getEnclosingPredicate() = c.getAClassPredicate() and + result = call.getBase() and + result.getBase() instanceof ThisAccess and + result.getTypeExpr().getResolvedType() = type + ) +} + +predicate usesFieldBasedInstanceof(Class c, TypeExpr type, VarDecl field, ComparisonFormula comp) { + exists(FieldAccess fieldAccess | + c.getCharPred().getBody() = comp or + c.getCharPred().getBody().(Conjunction).getAnOperand() = comp + | + comp.getOperator() = "=" and + comp.getEnclosingPredicate() = c.getCharPred() and + comp.getAnOperand() instanceof ThisAccess and + comp.getAnOperand() = fieldAccess and + fieldAccess.getDeclaration() = field and + field.getTypeExpr() = type + ) and + // require that there is a call to the range field that matches the name of the enclosing predicate + exists(FieldAccess access, MemberCall call | + access = getARangeFieldAccess(c, field, _) and + call.getBase() = access and + access.getEnclosingPredicate().getName() = call.getMemberName() + ) +} + +FieldAccess getARangeFieldAccess(Class c, VarDecl field, string name) { + exists(MemberCall call | + result = call.getBase() and + result.getDeclaration() = field and + name = call.getMemberName() and + call.getEnclosingPredicate().(ClassPredicate).getParent() = c + ) +} diff --git a/ql/ql/src/codeql_ql/style/UseSetLiteralQuery.qll b/ql/ql/src/codeql_ql/style/UseSetLiteralQuery.qll new file mode 100644 index 000000000000..5d08e1834d1e --- /dev/null +++ b/ql/ql/src/codeql_ql/style/UseSetLiteralQuery.qll @@ -0,0 +1,124 @@ +import ql + +/** + * 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 getOperand(int i) { + result = + rank[i + 1](Formula operand, Location l | + operand = getAnOperand*() and + not operand instanceof Disjunction and + l = operand.getLocation() + | + operand order by l.getStartLine(), l.getStartColumn() + ) + } +} + +/** + * An equality comparison with a `Literal`. For example: + * ``` + * x = 4 + * ``` + */ +class EqualsLiteral extends ComparisonFormula { + EqualsLiteral() { + getOperator() = "=" and + getAnOperand() instanceof Literal + } + + AstNode getOther() { + result = getAnOperand() and + not result instanceof Literal + } + + Literal getLiteral() { result = getAnOperand() } +} + +/** + * 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 { + AstNode firstOperand; + + DisjunctionEqualsLiteral() { + // VarAccess on the same variable + exists(VarDef v | + forex(Formula f | f = getOperand(_) | + f.(EqualsLiteral).getAnOperand().(VarAccess).getDeclaration() = v + ) and + firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and + firstOperand.(VarAccess).getDeclaration() = v + ) + or + // FieldAccess on the same variable + exists(VarDecl v | + forex(Formula f | f = getOperand(_) | + f.(EqualsLiteral).getAnOperand().(FieldAccess).getDeclaration() = v + ) and + firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and + firstOperand.(FieldAccess).getDeclaration() = v + ) + or + // ThisAccess + forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ThisAccess) and + firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ThisAccess) + or + // ResultAccess + forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ResultAccess) and + firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ResultAccess) + // (in principle something like GlobalValueNumbering could be used to generalize this) + } + + /** + * Gets the first "thing" that is the same thing in this chain of equalities. + */ + AstNode getFirstOperand() { result = firstOperand } +} + +/** + * 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 = getOperand(_) | f.(CallLiteral).getTarget() = target) + ) + } +} diff --git a/ql/ql/src/experimental/README.md b/ql/ql/src/experimental/README.md new file mode 100644 index 000000000000..ed83d8d4ab0e --- /dev/null +++ b/ql/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../docs/experimental.md) CodeQL queries and libraries. diff --git a/ql/ql/src/ide-contextual-queries/Definitions.qll b/ql/ql/src/ide-contextual-queries/Definitions.qll new file mode 100644 index 000000000000..eef1eb9b71d8 --- /dev/null +++ b/ql/ql/src/ide-contextual-queries/Definitions.qll @@ -0,0 +1,74 @@ +import ql +import codeql_ql.ast.internal.Module +import codeql.IDEContextual + +private newtype TLoc = + TAst(AstNode n) or + TFileOrModule(FileOrModule m) + +class Loc extends TLoc { + string toString() { result = "" } + + AstNode asAst() { this = TAst(result) } + + FileOrModule asMod() { this = TFileOrModule(result) } + + File getFile() { this.hasLocationInfo(result.getAbsolutePath(), _, _, _, _) } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(AstNode n | this = TAst(n) | + n.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + or + exists(FileOrModule m | this = TFileOrModule(m) | + m.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + } +} + +private predicate resolveModule(ModuleRef ref, FileOrModule target, string kind) { + target = ref.getResolvedModule() and + kind = "module" +} + +private predicate resolveType(TypeExpr ref, AstNode target, string kind) { + target = ref.getResolvedType().getDeclaration() and + kind = "type" +} + +private predicate resolvePredicate(PredicateExpr ref, Predicate target, string kind) { + target = ref.getResolvedPredicate() and + kind = "predicate" +} + +private predicate resolveVar(VarAccess va, VarDecl decl, string kind) { + decl = va.getDeclaration() and + kind = "variable" +} + +private predicate resolveField(FieldAccess va, VarDecl decl, string kind) { + decl = va.getDeclaration() and + kind = "field" +} + +private predicate resolveCall(Call c, Predicate p, string kind) { + p = c.getTarget() and + kind = "call" +} + +cached +predicate resolve(Loc ref, Loc target, string kind) { + resolveModule(ref.asAst(), target.asMod(), kind) + or + resolveType(ref.asAst(), target.asAst(), kind) + or + resolvePredicate(ref.asAst(), target.asAst(), kind) + or + resolveField(ref.asAst(), target.asAst(), kind) + or + resolveVar(ref.asAst(), target.asAst(), kind) + or + resolveCall(ref.asAst(), target.asAst(), kind) +} diff --git a/ql/ql/src/ide-contextual-queries/localDefinitions.ql b/ql/ql/src/ide-contextual-queries/localDefinitions.ql new file mode 100644 index 000000000000..ab7364f8101e --- /dev/null +++ b/ql/ql/src/ide-contextual-queries/localDefinitions.ql @@ -0,0 +1,20 @@ +/** + * @name Jump-to-definition links + * @description Generates use-definition pairs that provide the data + * for jump-to-definition in the code viewer. + * @kind definitions + * @id ql/ide-jump-to-definition + * @tags ide-contextual-queries/local-definitions + */ + +import ql +import Definitions +import codeql.IDEContextual + +external string selectedSourceFile(); + +from Loc ref, Loc target, string kind +where + resolve(ref, target, kind) and + ref.getFile() = getFileBySourceArchiveName(selectedSourceFile()) +select ref, target, kind diff --git a/ql/ql/src/ide-contextual-queries/localReferences.ql b/ql/ql/src/ide-contextual-queries/localReferences.ql new file mode 100644 index 000000000000..b637f9aa35cf --- /dev/null +++ b/ql/ql/src/ide-contextual-queries/localReferences.ql @@ -0,0 +1,20 @@ +/** + * @name Find-references links + * @description Generates use-definition pairs that provide the data + * for find-references in the code viewer. + * @kind definitions + * @id ql/ide-find-references + * @tags ide-contextual-queries/local-references + */ + +import ql +import Definitions +import codeql.IDEContextual + +external string selectedSourceFile(); + +from Loc ref, Loc target, string kind +where + resolve(ref, target, kind) and + target.getFile() = getFileBySourceArchiveName(selectedSourceFile()) +select ref, target, kind diff --git a/ql/ql/src/ide-contextual-queries/printAst.ql b/ql/ql/src/ide-contextual-queries/printAst.ql new file mode 100644 index 000000000000..1fd07db49d5e --- /dev/null +++ b/ql/ql/src/ide-contextual-queries/printAst.ql @@ -0,0 +1,28 @@ +/** + * @name Print AST + * @description Produces a representation of a file's Abstract Syntax Tree. + * This query is used by the VS Code extension. + * @id ql/print-ast + * @kind graph + * @tags ide-contextual-queries/print-ast + */ + +// Switch between the below two to switch between generated and pretty AST. +// import codeql_ql.printAstGenerated +import codeql_ql.printAstAst +import codeql.IDEContextual + +/** + * The source file to generate an AST from. + */ +external string selectedSourceFile(); + +/** + * Overrides the configuration to print only nodes in the selected source file. + */ +class Cfg extends PrintAstConfiguration { + override predicate shouldPrintNode(AstNode n) { + super.shouldPrintNode(n) and + n.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile()) + } +} diff --git a/ql/ql/src/ql.dbscheme b/ql/ql/src/ql.dbscheme new file mode 100644 index 000000000000..dbdd64930a39 --- /dev/null +++ b/ql/ql/src/ql.dbscheme @@ -0,0 +1,1152 @@ +// CodeQL database schema for QL +// Automatically generated from the tree-sitter grammar; do not edit + +@location = @location_default + +locations_default( + unique int id: @location_default, + int file: @file ref, + int start_line: int ref, + int start_column: int ref, + int end_line: int ref, + int end_column: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +folders( + unique int id: @folder, + string name: string ref +); + +@container = @file | @folder + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +sourceLocationPrefix( + string prefix: string ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +case @diagnostic.severity of + 10 = @diagnostic_debug +| 20 = @diagnostic_info +| 30 = @diagnostic_warning +| 40 = @diagnostic_error +; + + +@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 + +@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] +ql_ast_node_parent( + int child: @ql_ast_node ref, + int parent: @ql_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ql/ql/src/ql.dbscheme.stats b/ql/ql/src/ql.dbscheme.stats new file mode 100644 index 000000000000..665de5190c26 --- /dev/null +++ b/ql/ql/src/ql.dbscheme.stats @@ -0,0 +1,25558 @@ + + + @alias + 398 + + + @argument_list + 215845 + + + @array + 10382 + + + @assignment + 39638 + + + @bare_string + 3009 + + + @bare_symbol + 687 + + + @begin + 613 + + + @begin_block + 0 + + + @binary_ampersand + 41 + + + @binary_ampersandampersand + 2775 + + + @binary_and + 85 + + + @binary_bangequal + 498 + + + @binary_bangtilde + 36 + + + @binary_caret + 29 + + + @binary_equalequal + 2506 + + + @binary_equalequalequal + 173 + + + @binary_equaltilde + 237 + + + @binary_langle + 430 + + + @binary_langleequal + 84 + + + @binary_langleequalrangle + 83 + + + @binary_langlelangle + 3285 + + + @binary_minus + 624 + + + @binary_or + 3 + + + @binary_percent + 140 + + + @binary_pipe + 42 + + + @binary_pipepipe + 2531 + + + @binary_plus + 1490 + + + @binary_rangle + 772 + + + @binary_rangleequal + 125 + + + @binary_ranglerangle + 6 + + + @binary_slash + 136 + + + @binary_star + 364 + + + @binary_starstar + 33 + + + @block + 21214 + + + @block_argument + 1770 + + + @block_parameter + 658 + + + @block_parameters + 7227 + + + @break + 214 + + + @call + 303820 + + + @case__ + 377 + + + @chained_string + 268 + + + @class + 5194 + + + @conditional + 1120 + + + @delimited_symbol + 384 + + + @destructured_left_assignment + 1 + + + @destructured_parameter + 62 + + + @diagnostic_debug + 0 + + + @diagnostic_error + 64 + + + @diagnostic_info + 0 + + + @diagnostic_warning + 0 + + + @do + 117 + + + @do_block + 41659 + + + @element_reference + 25582 + + + @else + 2122 + + + @elsif + 491 + + + @end_block + 0 + + + @ensure + 1125 + + + @exception_variable + 309 + + + @exceptions + 429 + + + @file + 6322 + + + @folder + 1489 + + + @for + 1 + + + @hash + 8122 + + + @hash_splat_argument + 398 + + + @hash_splat_parameter + 413 + + + @heredoc_body + 1610 + + + @if + 5713 + + + @if_modifier + 4276 + + + @in + 1 + + + @interpolation + 11773 + + + @keyword_parameter + 1099 + + + @lambda + 660 + + + @lambda_parameters + 190 + + + @left_assignment_list + 773 + + + @location_default + 2604972 + + + @method + 30455 + + + @method_parameters + 8920 + + + @module + 4441 + + + @next + 653 + + + @operator_assignment_ampersandampersandequal + 5 + + + @operator_assignment_ampersandequal + 5 + + + @operator_assignment_caretequal + 0 + + + @operator_assignment_langlelangleequal + 0 + + + @operator_assignment_minusequal + 62 + + + @operator_assignment_percentequal + 2 + + + @operator_assignment_pipeequal + 44 + + + @operator_assignment_pipepipeequal + 1417 + + + @operator_assignment_plusequal + 506 + + + @operator_assignment_ranglerangleequal + 0 + + + @operator_assignment_slashequal + 3 + + + @operator_assignment_starequal + 2 + + + @operator_assignment_starstarequal + 0 + + + @optional_parameter + 2047 + + + @pair + 62127 + + + @parenthesized_statements + 1652 + + + @pattern + 1186 + + + @program + 6322 + + + @range_dotdot + 424 + + + @range_dotdotdot + 122 + + + @rational + 2 + + + @redo + 0 + + + @regex + 3979 + + + @rescue + 634 + + + @rescue_modifier + 174 + + + @reserved_word + 1009882 + + + @rest_assignment + 18 + + + @retry + 9 + + + @return + 2601 + + + @right_assignment_list + 414 + + + @scope_resolution + 22918 + + + @setter + 186 + + + @singleton_class + 191 + + + @singleton_method + 2020 + + + @splat_argument + 683 + + + @splat_parameter + 921 + + + @string__ + 91253 + + + @string_array + 938 + + + @subshell + 130 + + + @superclass + 4108 + + + @symbol_array + 139 + + + @then + 7609 + + + @token_character + 11 + + + @token_class_variable + 238 + + + @token_comment + 55974 + + + @token_complex + 0 + + + @token_constant + 86706 + + + @token_empty_statement + 0 + + + @token_escape_sequence + 19932 + + + @token_false + 5175 + + + @token_float + 3122 + + + @token_global_variable + 724 + + + @token_hash_key_symbol + 60562 + + + @token_heredoc_beginning + 1610 + + + @token_heredoc_content + 3662 + + + @token_heredoc_end + 1610 + + + @token_identifier + 456774 + + + @token_instance_variable + 25234 + + + @token_integer + 32694 + + + @token_nil + 4047 + + + @token_operator + 195 + + + @token_self + 4040 + + + @token_simple_symbol + 82353 + + + @token_string_content + 115192 + + + @token_super + 1554 + + + @token_true + 7308 + + + @token_uninterpreted + 0 + + + @unary_bang + 1682 + + + @unary_definedquestion + 247 + + + @unary_minus + 651 + + + @unary_not + 10 + + + @unary_plus + 436 + + + @unary_tilde + 5 + + + @undef + 13 + + + @unless + 497 + + + @unless_modifier + 1404 + + + @until + 16 + + + @until_modifier + 13 + + + @when + 987 + + + @while + 106 + + + @while_modifier + 9 + + + @yield + 841 + + + + alias_def + 398 + + + id + 398 + + + alias + 398 + + + name + 398 + + + loc + 398 + + + + + id + alias + + + 12 + + + 1 + 2 + 398 + + + + + + + id + name + + + 12 + + + 1 + 2 + 398 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 398 + + + + + + + alias + id + + + 12 + + + 1 + 2 + 398 + + + + + + + alias + name + + + 12 + + + 1 + 2 + 398 + + + + + + + alias + loc + + + 12 + + + 1 + 2 + 398 + + + + + + + name + id + + + 12 + + + 1 + 2 + 398 + + + + + + + name + alias + + + 12 + + + 1 + 2 + 398 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 398 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 398 + + + + + + + loc + alias + + + 12 + + + 1 + 2 + 398 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 398 + + + + + + + + + argument_list_child + 276770 + + + argument_list + 215777 + + + index + 109 + + + child + 276770 + + + + + argument_list + index + + + 12 + + + 1 + 2 + 176126 + + + 2 + 3 + 26678 + + + 3 + 33 + 12972 + + + + + + + argument_list + child + + + 12 + + + 1 + 2 + 176126 + + + 2 + 3 + 26678 + + + 3 + 33 + 12972 + + + + + + + index + argument_list + + + 12 + + + 1 + 2 + 37 + + + 2 + 3 + 10 + + + 3 + 4 + 3 + + + 4 + 5 + 6 + + + 5 + 8 + 6 + + + 9 + 12 + 6 + + + 14 + 17 + 6 + + + 24 + 43 + 6 + + + 103 + 235 + 6 + + + 535 + 1428 + 6 + + + 3806 + 11634 + 6 + + + 63305 + 63306 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 37 + + + 2 + 3 + 10 + + + 3 + 4 + 3 + + + 4 + 5 + 6 + + + 5 + 8 + 6 + + + 9 + 12 + 6 + + + 14 + 17 + 6 + + + 24 + 43 + 6 + + + 103 + 235 + 6 + + + 535 + 1428 + 6 + + + 3806 + 11634 + 6 + + + 63305 + 63306 + 3 + + + + + + + child + argument_list + + + 12 + + + 1 + 2 + 276770 + + + + + + + child + index + + + 12 + + + 1 + 2 + 276770 + + + + + + + + + argument_list_def + 215845 + + + id + 215845 + + + loc + 215845 + + + + + id + loc + + + 12 + + + 1 + 2 + 215845 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 215845 + + + + + + + + + array_child + 19631 + + + array + 8772 + + + index + 93 + + + child + 19631 + + + + + array + index + + + 12 + + + 1 + 2 + 2906 + + + 2 + 3 + 3796 + + + 3 + 4 + 1270 + + + 4 + 9 + 667 + + + 9 + 94 + 133 + + + + + + + array + child + + + 12 + + + 1 + 2 + 2906 + + + 2 + 3 + 3796 + + + 3 + 4 + 1270 + + + 4 + 9 + 667 + + + 9 + 94 + 133 + + + + + + + index + array + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 27 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 11 + 7 + + + 12 + 21 + 7 + + + 23 + 35 + 7 + + + 36 + 134 + 7 + + + 169 + 5867 + 7 + + + 8772 + 8773 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 27 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 11 + 7 + + + 12 + 21 + 7 + + + 23 + 35 + 7 + + + 36 + 134 + 7 + + + 169 + 5867 + 7 + + + 8772 + 8773 + 1 + + + + + + + child + array + + + 12 + + + 1 + 2 + 19631 + + + + + + + child + index + + + 12 + + + 1 + 2 + 19631 + + + + + + + + + array_def + 10382 + + + id + 10382 + + + loc + 10382 + + + + + id + loc + + + 12 + + + 1 + 2 + 10382 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 10382 + + + + + + + + + assignment_def + 39638 + + + id + 39638 + + + left + 39638 + + + right + 39638 + + + loc + 39638 + + + + + id + left + + + 12 + + + 1 + 2 + 39638 + + + + + + + id + right + + + 12 + + + 1 + 2 + 39638 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 39638 + + + + + + + left + id + + + 12 + + + 1 + 2 + 39638 + + + + + + + left + right + + + 12 + + + 1 + 2 + 39638 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 39638 + + + + + + + right + id + + + 12 + + + 1 + 2 + 39638 + + + + + + + right + left + + + 12 + + + 1 + 2 + 39638 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 39638 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 39638 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 39638 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 39638 + + + + + + + + + ast_node_parent + 2689484 + + + child + 2689484 + + + parent + 882787 + + + parent_index + 586 + + + + + child + parent + + + 12 + + + 1 + 2 + 2689484 + + + + + + + child + parent_index + + + 12 + + + 1 + 2 + 2689484 + + + + + + + parent + child + + + 12 + + + 1 + 2 + 95994 + + + 2 + 3 + 128409 + + + 3 + 4 + 476622 + + + 4 + 5 + 115559 + + + 5 + 173 + 66200 + + + + + + + parent + parent_index + + + 12 + + + 1 + 2 + 95994 + + + 2 + 3 + 128409 + + + 3 + 4 + 476622 + + + 4 + 5 + 115559 + + + 5 + 173 + 66200 + + + + + + + parent_index + child + + + 12 + + + 1 + 2 + 126 + + + 2 + 3 + 68 + + + 3 + 4 + 54 + + + 4 + 5 + 13 + + + 5 + 7 + 51 + + + 7 + 15 + 51 + + + 15 + 27 + 47 + + + 28 + 58 + 44 + + + 61 + 147 + 44 + + + 161 + 923 + 44 + + + 1058 + 258994 + 40 + + + + + + + parent_index + parent + + + 12 + + + 1 + 2 + 126 + + + 2 + 3 + 68 + + + 3 + 4 + 54 + + + 4 + 5 + 13 + + + 5 + 7 + 51 + + + 7 + 15 + 51 + + + 15 + 27 + 47 + + + 28 + 58 + 44 + + + 61 + 147 + 44 + + + 161 + 923 + 44 + + + 1058 + 258994 + 40 + + + + + + + + + bare_string_child + 3021 + + + bare_string + 3009 + + + index + 2 + + + child + 3021 + + + + + bare_string + index + + + 12 + + + 1 + 2 + 2997 + + + 2 + 3 + 12 + + + + + + + bare_string + child + + + 12 + + + 1 + 2 + 2997 + + + 2 + 3 + 12 + + + + + + + index + bare_string + + + 12 + + + 12 + 13 + 1 + + + 3009 + 3010 + 1 + + + + + + + index + child + + + 12 + + + 12 + 13 + 1 + + + 3009 + 3010 + 1 + + + + + + + child + bare_string + + + 12 + + + 1 + 2 + 3021 + + + + + + + child + index + + + 12 + + + 1 + 2 + 3021 + + + + + + + + + bare_string_def + 3009 + + + id + 3009 + + + loc + 3009 + + + + + id + loc + + + 12 + + + 1 + 2 + 3009 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 3009 + + + + + + + + + bare_symbol_child + 687 + + + bare_symbol + 687 + + + index + 1 + + + child + 687 + + + + + bare_symbol + index + + + 12 + + + 1 + 2 + 687 + + + + + + + bare_symbol + child + + + 12 + + + 1 + 2 + 687 + + + + + + + index + bare_symbol + + + 12 + + + 672 + 673 + 1 + + + + + + + index + child + + + 12 + + + 672 + 673 + 1 + + + + + + + child + bare_symbol + + + 12 + + + 1 + 2 + 687 + + + + + + + child + index + + + 12 + + + 1 + 2 + 687 + + + + + + + + + bare_symbol_def + 687 + + + id + 687 + + + loc + 687 + + + + + id + loc + + + 12 + + + 1 + 2 + 687 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 687 + + + + + + + + + begin_block_child + 0 + + + begin_block + 0 + + + index + 0 + + + child + 0 + + + + + begin_block + index + + + 12 + + + + + + begin_block + child + + + 12 + + + + + + index + begin_block + + + 12 + + + + + + index + child + + + 12 + + + + + + child + begin_block + + + 12 + + + 1 + 2 + 1 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1 + + + + + + + + + begin_block_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + begin_child + 2106 + + + begin + 613 + + + index + 34 + + + child + 2106 + + + + + begin + index + + + 12 + + + 1 + 2 + 31 + + + 2 + 3 + 278 + + + 3 + 4 + 126 + + + 4 + 5 + 68 + + + 5 + 7 + 52 + + + 7 + 13 + 46 + + + 13 + 35 + 9 + + + + + + + begin + child + + + 12 + + + 1 + 2 + 31 + + + 2 + 3 + 278 + + + 3 + 4 + 126 + + + 4 + 5 + 68 + + + 5 + 7 + 52 + + + 7 + 13 + 46 + + + 13 + 35 + 9 + + + + + + + index + begin + + + 12 + + + 1 + 2 + 6 + + + 4 + 5 + 14 + + + 5 + 12 + 3 + + + 14 + 30 + 3 + + + 41 + 74 + 3 + + + 105 + 297 + 3 + + + 568 + 600 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 6 + + + 4 + 5 + 14 + + + 5 + 12 + 3 + + + 14 + 30 + 3 + + + 41 + 74 + 3 + + + 105 + 297 + 3 + + + 568 + 600 + 2 + + + + + + + child + begin + + + 12 + + + 1 + 2 + 2106 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2106 + + + + + + + + + begin_def + 613 + + + id + 613 + + + loc + 613 + + + + + id + loc + + + 12 + + + 1 + 2 + 613 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 613 + + + + + + + + + binary_def + 13969 + + + id + 13969 + + + left + 13969 + + + operator + 23 + + + right + 13969 + + + loc + 13969 + + + + + id + left + + + 12 + + + 1 + 2 + 13969 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 13969 + + + + + + + id + right + + + 12 + + + 1 + 2 + 13969 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 13969 + + + + + + + left + id + + + 12 + + + 1 + 2 + 13969 + + + + + + + left + operator + + + 12 + + + 1 + 2 + 13969 + + + + + + + left + right + + + 12 + + + 1 + 2 + 13969 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 13969 + + + + + + + operator + id + + + 12 + + + 1 + 2 + 2 + + + 9 + 15 + 2 + + + 22 + 23 + 1 + + + 36 + 37 + 2 + + + 83 + 98 + 2 + + + 114 + 115 + 1 + + + 123 + 124 + 2 + + + 232 + 303 + 2 + + + 421 + 488 + 2 + + + 610 + 756 + 2 + + + 1172 + 1377 + 2 + + + 2448 + 2474 + 2 + + + 2711 + 2712 + 1 + + + + + + + operator + left + + + 12 + + + 1 + 2 + 2 + + + 9 + 15 + 2 + + + 22 + 23 + 1 + + + 36 + 37 + 2 + + + 83 + 98 + 2 + + + 114 + 115 + 1 + + + 123 + 124 + 2 + + + 232 + 303 + 2 + + + 421 + 488 + 2 + + + 610 + 756 + 2 + + + 1172 + 1377 + 2 + + + 2448 + 2474 + 2 + + + 2711 + 2712 + 1 + + + + + + + operator + right + + + 12 + + + 1 + 2 + 2 + + + 9 + 15 + 2 + + + 22 + 23 + 1 + + + 36 + 37 + 2 + + + 83 + 98 + 2 + + + 114 + 115 + 1 + + + 123 + 124 + 2 + + + 232 + 303 + 2 + + + 421 + 488 + 2 + + + 610 + 756 + 2 + + + 1172 + 1377 + 2 + + + 2448 + 2474 + 2 + + + 2711 + 2712 + 1 + + + + + + + operator + loc + + + 12 + + + 1 + 2 + 2 + + + 9 + 15 + 2 + + + 22 + 23 + 1 + + + 36 + 37 + 2 + + + 83 + 98 + 2 + + + 114 + 115 + 1 + + + 123 + 124 + 2 + + + 232 + 303 + 2 + + + 421 + 488 + 2 + + + 610 + 756 + 2 + + + 1172 + 1377 + 2 + + + 2448 + 2474 + 2 + + + 2711 + 2712 + 1 + + + + + + + right + id + + + 12 + + + 1 + 2 + 13969 + + + + + + + right + left + + + 12 + + + 1 + 2 + 13969 + + + + + + + right + operator + + + 12 + + + 1 + 2 + 13969 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 13969 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 13969 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 13969 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 13969 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 13969 + + + + + + + + + block_argument_def + 1770 + + + id + 1770 + + + child + 1770 + + + loc + 1770 + + + + + id + child + + + 12 + + + 1 + 2 + 1770 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1770 + + + + + + + child + id + + + 12 + + + 1 + 2 + 1770 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 1770 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1770 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 1770 + + + + + + + + + block_child + 21204 + + + block + 21163 + + + index + 13 + + + child + 21204 + + + + + block + index + + + 12 + + + 1 + 2 + 21136 + + + 2 + 5 + 27 + + + + + + + block + child + + + 12 + + + 1 + 2 + 21136 + + + 2 + 5 + 27 + + + + + + + index + block + + + 12 + + + 1 + 2 + 3 + + + 3 + 4 + 3 + + + 8 + 9 + 3 + + + 6209 + 6210 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 3 + + + 3 + 4 + 3 + + + 8 + 9 + 3 + + + 6209 + 6210 + 3 + + + + + + + child + block + + + 12 + + + 1 + 2 + 21204 + + + + + + + child + index + + + 12 + + + 1 + 2 + 21204 + + + + + + + + + block_def + 21214 + + + id + 21214 + + + loc + 21214 + + + + + id + loc + + + 12 + + + 1 + 2 + 21214 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 21214 + + + + + + + + + block_parameter_def + 658 + + + id + 658 + + + name + 658 + + + loc + 658 + + + + + id + name + + + 12 + + + 1 + 2 + 658 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 658 + + + + + + + name + id + + + 12 + + + 1 + 2 + 658 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 658 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 658 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 658 + + + + + + + + + block_parameters + 2662 + + + block + 2662 + + + parameters + 2662 + + + + + block + parameters + + + 12 + + + 1 + 2 + 2662 + + + + + + + parameters + block + + + 12 + + + 1 + 2 + 2662 + + + + + + + + + block_parameters_child + 8428 + + + block_parameters + 7227 + + + index + 5 + + + child + 8428 + + + + + block_parameters + index + + + 12 + + + 1 + 2 + 6166 + + + 2 + 3 + 963 + + + 3 + 6 + 98 + + + + + + + block_parameters + child + + + 12 + + + 1 + 2 + 6166 + + + 2 + 3 + 963 + + + 3 + 6 + 98 + + + + + + + index + block_parameters + + + 12 + + + 9 + 10 + 1 + + + 33 + 34 + 1 + + + 98 + 99 + 1 + + + 1061 + 1062 + 1 + + + 7227 + 7228 + 1 + + + + + + + index + child + + + 12 + + + 9 + 10 + 1 + + + 33 + 34 + 1 + + + 98 + 99 + 1 + + + 1061 + 1062 + 1 + + + 7227 + 7228 + 1 + + + + + + + child + block_parameters + + + 12 + + + 1 + 2 + 8428 + + + + + + + child + index + + + 12 + + + 1 + 2 + 8428 + + + + + + + + + block_parameters_def + 7227 + + + id + 7227 + + + loc + 7227 + + + + + id + loc + + + 12 + + + 1 + 2 + 7227 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 7227 + + + + + + + + + break_child + 10 + + + break + 10 + + + child + 10 + + + + + break + child + + + 12 + + + 1 + 2 + 10 + + + + + + + child + break + + + 12 + + + 1 + 2 + 10 + + + + + + + + + break_def + 214 + + + id + 214 + + + loc + 214 + + + + + id + loc + + + 12 + + + 1 + 2 + 214 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 214 + + + + + + + + + call_arguments + 215041 + + + call + 215041 + + + arguments + 215041 + + + + + call + arguments + + + 12 + + + 1 + 2 + 215041 + + + + + + + arguments + call + + + 12 + + + 1 + 2 + 215041 + + + + + + + + + call_block + 62301 + + + call + 62301 + + + block + 62301 + + + + + call + block + + + 12 + + + 1 + 2 + 62301 + + + + + + + block + call + + + 12 + + + 1 + 2 + 62301 + + + + + + + + + call_def + 303820 + + + id + 303820 + + + method + 303820 + + + loc + 303820 + + + + + id + method + + + 12 + + + 1 + 2 + 303820 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 303820 + + + + + + + method + id + + + 12 + + + 1 + 2 + 303820 + + + + + + + method + loc + + + 12 + + + 1 + 2 + 303820 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 303820 + + + + + + + loc + method + + + 12 + + + 1 + 2 + 303820 + + + + + + + + + call_receiver + 166508 + + + call + 166508 + + + receiver + 166508 + + + + + call + receiver + + + 12 + + + 1 + 2 + 166508 + + + + + + + receiver + call + + + 12 + + + 1 + 2 + 166508 + + + + + + + + + case_child + 1267 + + + case__ + 377 + + + index + 22 + + + child + 1267 + + + + + case__ + index + + + 12 + + + 1 + 2 + 10 + + + 2 + 3 + 102 + + + 3 + 4 + 156 + + + 4 + 5 + 60 + + + 5 + 7 + 29 + + + 7 + 23 + 20 + + + + + + + case__ + child + + + 12 + + + 1 + 2 + 10 + + + 2 + 3 + 102 + + + 3 + 4 + 156 + + + 4 + 5 + 60 + + + 5 + 7 + 29 + + + 7 + 23 + 20 + + + + + + + index + case__ + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 2 + + + 3 + 5 + 2 + + + 8 + 11 + 2 + + + 13 + 21 + 2 + + + 30 + 50 + 2 + + + 109 + 266 + 2 + + + 367 + 378 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 2 + + + 3 + 5 + 2 + + + 8 + 11 + 2 + + + 13 + 21 + 2 + + + 30 + 50 + 2 + + + 109 + 266 + 2 + + + 367 + 378 + 2 + + + + + + + child + case__ + + + 12 + + + 1 + 2 + 1267 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1267 + + + + + + + + + case_def + 377 + + + id + 377 + + + loc + 377 + + + + + id + loc + + + 12 + + + 1 + 2 + 377 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 377 + + + + + + + + + case_value + 365 + + + case__ + 365 + + + value + 365 + + + + + case__ + value + + + 12 + + + 1 + 2 + 365 + + + + + + + value + case__ + + + 12 + + + 1 + 2 + 365 + + + + + + + + + chained_string_child + 1031 + + + chained_string + 268 + + + index + 12 + + + child + 1031 + + + + + chained_string + index + + + 12 + + + 2 + 3 + 85 + + + 3 + 4 + 62 + + + 4 + 5 + 43 + + + 5 + 6 + 38 + + + 6 + 8 + 20 + + + 8 + 13 + 20 + + + + + + + chained_string + child + + + 12 + + + 2 + 3 + 85 + + + 3 + 4 + 62 + + + 4 + 5 + 43 + + + 5 + 6 + 38 + + + 6 + 8 + 20 + + + 8 + 13 + 20 + + + + + + + index + chained_string + + + 12 + + + 2 + 3 + 1 + + + 4 + 5 + 1 + + + 7 + 8 + 1 + + + 8 + 9 + 1 + + + 20 + 21 + 1 + + + 32 + 33 + 1 + + + 40 + 41 + 1 + + + 78 + 79 + 1 + + + 121 + 122 + 1 + + + 183 + 184 + 1 + + + 268 + 269 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 1 + + + 4 + 5 + 1 + + + 7 + 8 + 1 + + + 8 + 9 + 1 + + + 20 + 21 + 1 + + + 32 + 33 + 1 + + + 40 + 41 + 1 + + + 78 + 79 + 1 + + + 121 + 122 + 1 + + + 183 + 184 + 1 + + + 268 + 269 + 2 + + + + + + + child + chained_string + + + 12 + + + 1 + 2 + 1031 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1031 + + + + + + + + + chained_string_def + 268 + + + id + 268 + + + loc + 268 + + + + + id + loc + + + 12 + + + 1 + 2 + 268 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 268 + + + + + + + + + class_child + 40498 + + + class + 4669 + + + index + 305 + + + child + 40498 + + + + + class + index + + + 12 + + + 1 + 2 + 1013 + + + 2 + 3 + 726 + + + 3 + 4 + 465 + + + 4 + 5 + 399 + + + 5 + 6 + 296 + + + 6 + 8 + 430 + + + 8 + 11 + 402 + + + 11 + 16 + 373 + + + 16 + 33 + 355 + + + 33 + 306 + 210 + + + + + + + class + child + + + 12 + + + 1 + 2 + 1013 + + + 2 + 3 + 726 + + + 3 + 4 + 465 + + + 4 + 5 + 399 + + + 5 + 6 + 296 + + + 6 + 8 + 430 + + + 8 + 11 + 402 + + + 11 + 16 + 373 + + + 16 + 33 + 355 + + + 33 + 306 + 210 + + + + + + + index + class + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 25 + + + 3 + 4 + 8 + + + 4 + 5 + 24 + + + 5 + 7 + 24 + + + 7 + 8 + 24 + + + 8 + 10 + 12 + + + 10 + 12 + 24 + + + 12 + 17 + 25 + + + 17 + 30 + 24 + + + 31 + 50 + 24 + + + 52 + 86 + 23 + + + 86 + 177 + 23 + + + 183 + 638 + 23 + + + 698 + 4670 + 14 + + + + + + + index + child + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 25 + + + 3 + 4 + 8 + + + 4 + 5 + 24 + + + 5 + 7 + 24 + + + 7 + 8 + 24 + + + 8 + 10 + 12 + + + 10 + 12 + 24 + + + 12 + 17 + 25 + + + 17 + 30 + 24 + + + 31 + 50 + 24 + + + 52 + 86 + 23 + + + 86 + 177 + 23 + + + 183 + 638 + 23 + + + 698 + 4670 + 14 + + + + + + + child + class + + + 12 + + + 1 + 2 + 40498 + + + + + + + child + index + + + 12 + + + 1 + 2 + 40498 + + + + + + + + + class_def + 5194 + + + id + 5194 + + + name + 5194 + + + loc + 5194 + + + + + id + name + + + 12 + + + 1 + 2 + 5194 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 5194 + + + + + + + name + id + + + 12 + + + 1 + 2 + 5194 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 5194 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 5194 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 5194 + + + + + + + + + class_superclass + 4108 + + + class + 4108 + + + superclass + 4108 + + + + + class + superclass + + + 12 + + + 1 + 2 + 4108 + + + + + + + superclass + class + + + 12 + + + 1 + 2 + 4108 + + + + + + + + + conditional_def + 1120 + + + id + 1120 + + + alternative + 1120 + + + condition + 1120 + + + consequence + 1120 + + + loc + 1120 + + + + + id + alternative + + + 12 + + + 1 + 2 + 1120 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 1120 + + + + + + + id + consequence + + + 12 + + + 1 + 2 + 1120 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1120 + + + + + + + alternative + id + + + 12 + + + 1 + 2 + 1120 + + + + + + + alternative + condition + + + 12 + + + 1 + 2 + 1120 + + + + + + + alternative + consequence + + + 12 + + + 1 + 2 + 1120 + + + + + + + alternative + loc + + + 12 + + + 1 + 2 + 1120 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 1120 + + + + + + + condition + alternative + + + 12 + + + 1 + 2 + 1120 + + + + + + + condition + consequence + + + 12 + + + 1 + 2 + 1120 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 1120 + + + + + + + consequence + id + + + 12 + + + 1 + 2 + 1120 + + + + + + + consequence + alternative + + + 12 + + + 1 + 2 + 1120 + + + + + + + consequence + condition + + + 12 + + + 1 + 2 + 1120 + + + + + + + consequence + loc + + + 12 + + + 1 + 2 + 1120 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1120 + + + + + + + loc + alternative + + + 12 + + + 1 + 2 + 1120 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 1120 + + + + + + + loc + consequence + + + 12 + + + 1 + 2 + 1120 + + + + + + + + + containerparent + 7808 + + + parent + 1489 + + + child + 7808 + + + + + parent + child + + + 12 + + + 1 + 2 + 538 + + + 2 + 3 + 306 + + + 3 + 4 + 119 + + + 4 + 5 + 160 + + + 5 + 6 + 102 + + + 6 + 10 + 115 + + + 10 + 30 + 112 + + + 30 + 295 + 34 + + + + + + + child + parent + + + 12 + + + 1 + 2 + 7808 + + + + + + + + + delimited_symbol_child + 529 + + + delimited_symbol + 384 + + + index + 8 + + + child + 529 + + + + + delimited_symbol + index + + + 12 + + + 1 + 2 + 290 + + + 2 + 3 + 72 + + + 3 + 9 + 22 + + + + + + + delimited_symbol + child + + + 12 + + + 1 + 2 + 290 + + + 2 + 3 + 72 + + + 3 + 9 + 22 + + + + + + + index + delimited_symbol + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 8 + 9 + 1 + + + 12 + 13 + 1 + + + 22 + 23 + 1 + + + 94 + 95 + 1 + + + 384 + 385 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 8 + 9 + 1 + + + 12 + 13 + 1 + + + 22 + 23 + 1 + + + 94 + 95 + 1 + + + 384 + 385 + 1 + + + + + + + child + delimited_symbol + + + 12 + + + 1 + 2 + 529 + + + + + + + child + index + + + 12 + + + 1 + 2 + 529 + + + + + + + + + delimited_symbol_def + 384 + + + id + 384 + + + loc + 384 + + + + + id + loc + + + 12 + + + 1 + 2 + 384 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 384 + + + + + + + + + destructured_left_assignment_child + 2 + + + destructured_left_assignment + 1 + + + index + 2 + + + child + 2 + + + + + destructured_left_assignment + index + + + 12 + + + 2 + 3 + 1 + + + + + + + destructured_left_assignment + child + + + 12 + + + 2 + 3 + 1 + + + + + + + index + destructured_left_assignment + + + 12 + + + 1 + 2 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 2 + + + + + + + child + destructured_left_assignment + + + 12 + + + 1 + 2 + 2 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2 + + + + + + + + + destructured_left_assignment_def + 1 + + + id + 1 + + + loc + 1 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1 + + + + + + + + + destructured_parameter_child + 127 + + + destructured_parameter + 62 + + + index + 4 + + + child + 127 + + + + + destructured_parameter + index + + + 12 + + + 1 + 2 + 3 + + + 2 + 3 + 54 + + + 3 + 5 + 5 + + + + + + + destructured_parameter + child + + + 12 + + + 1 + 2 + 3 + + + 2 + 3 + 54 + + + 3 + 5 + 5 + + + + + + + index + destructured_parameter + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 59 + 60 + 1 + + + 62 + 63 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 59 + 60 + 1 + + + 62 + 63 + 1 + + + + + + + child + destructured_parameter + + + 12 + + + 1 + 2 + 127 + + + + + + + child + index + + + 12 + + + 1 + 2 + 127 + + + + + + + + + destructured_parameter_def + 62 + + + id + 62 + + + loc + 62 + + + + + id + loc + + + 12 + + + 1 + 2 + 62 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 62 + + + + + + + + + diagnostics + 64 + + + id + 64 + + + severity + 3 + + + error_tag + 3 + + + error_message + 13 + + + full_error_message + 61 + + + location + 64 + + + + + id + severity + + + 12 + + + 1 + 2 + 64 + + + + + + + id + error_tag + + + 12 + + + 1 + 2 + 64 + + + + + + + id + error_message + + + 12 + + + 1 + 2 + 64 + + + + + + + id + full_error_message + + + 12 + + + 1 + 2 + 64 + + + + + + + id + location + + + 12 + + + 1 + 2 + 64 + + + + + + + severity + id + + + 12 + + + 19 + 20 + 3 + + + + + + + severity + error_tag + + + 12 + + + 1 + 2 + 3 + + + + + + + severity + error_message + + + 12 + + + 4 + 5 + 3 + + + + + + + severity + full_error_message + + + 12 + + + 18 + 19 + 3 + + + + + + + severity + location + + + 12 + + + 19 + 20 + 3 + + + + + + + error_tag + id + + + 12 + + + 19 + 20 + 3 + + + + + + + error_tag + severity + + + 12 + + + 1 + 2 + 3 + + + + + + + error_tag + error_message + + + 12 + + + 4 + 5 + 3 + + + + + + + error_tag + full_error_message + + + 12 + + + 18 + 19 + 3 + + + + + + + error_tag + location + + + 12 + + + 19 + 20 + 3 + + + + + + + error_message + id + + + 12 + + + 1 + 2 + 10 + + + 16 + 17 + 3 + + + + + + + error_message + severity + + + 12 + + + 1 + 2 + 13 + + + + + + + error_message + error_tag + + + 12 + + + 1 + 2 + 13 + + + + + + + error_message + full_error_message + + + 12 + + + 1 + 2 + 10 + + + 15 + 16 + 3 + + + + + + + error_message + location + + + 12 + + + 1 + 2 + 10 + + + 16 + 17 + 3 + + + + + + + full_error_message + id + + + 12 + + + 1 + 2 + 57 + + + 2 + 3 + 3 + + + + + + + full_error_message + severity + + + 12 + + + 1 + 2 + 61 + + + + + + + full_error_message + error_tag + + + 12 + + + 1 + 2 + 61 + + + + + + + full_error_message + error_message + + + 12 + + + 1 + 2 + 61 + + + + + + + full_error_message + location + + + 12 + + + 1 + 2 + 57 + + + 2 + 3 + 3 + + + + + + + location + id + + + 12 + + + 1 + 2 + 64 + + + + + + + location + severity + + + 12 + + + 1 + 2 + 64 + + + + + + + location + error_tag + + + 12 + + + 1 + 2 + 64 + + + + + + + location + error_message + + + 12 + + + 1 + 2 + 64 + + + + + + + location + full_error_message + + + 12 + + + 1 + 2 + 64 + + + + + + + + + do_block_child + 121320 + + + do_block + 41642 + + + index + 245 + + + child + 121320 + + + + + do_block + index + + + 12 + + + 1 + 2 + 13351 + + + 2 + 3 + 11657 + + + 3 + 4 + 6680 + + + 4 + 5 + 3602 + + + 5 + 7 + 3347 + + + 7 + 73 + 3002 + + + + + + + do_block + child + + + 12 + + + 1 + 2 + 13351 + + + 2 + 3 + 11657 + + + 3 + 4 + 6680 + + + 4 + 5 + 3602 + + + 5 + 7 + 3347 + + + 7 + 73 + 3002 + + + + + + + index + do_block + + + 12 + + + 1 + 2 + 47 + + + 2 + 3 + 37 + + + 3 + 5 + 20 + + + 5 + 7 + 20 + + + 8 + 16 + 20 + + + 17 + 33 + 20 + + + 35 + 71 + 20 + + + 84 + 245 + 20 + + + 294 + 1246 + 20 + + + 1863 + 12218 + 17 + + + + + + + index + child + + + 12 + + + 1 + 2 + 47 + + + 2 + 3 + 37 + + + 3 + 5 + 20 + + + 5 + 7 + 20 + + + 8 + 16 + 20 + + + 17 + 33 + 20 + + + 35 + 71 + 20 + + + 84 + 245 + 20 + + + 294 + 1246 + 20 + + + 1863 + 12218 + 17 + + + + + + + child + do_block + + + 12 + + + 1 + 2 + 121320 + + + + + + + child + index + + + 12 + + + 1 + 2 + 121320 + + + + + + + + + do_block_def + 41659 + + + id + 41659 + + + loc + 41659 + + + + + id + loc + + + 12 + + + 1 + 2 + 41659 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 41659 + + + + + + + + + do_block_parameters + 4565 + + + do_block + 4565 + + + parameters + 4565 + + + + + do_block + parameters + + + 12 + + + 1 + 2 + 4565 + + + + + + + parameters + do_block + + + 12 + + + 1 + 2 + 4565 + + + + + + + + + do_child + 268 + + + do + 114 + + + index + 18 + + + child + 268 + + + + + do + index + + + 12 + + + 1 + 2 + 34 + + + 2 + 3 + 48 + + + 3 + 4 + 17 + + + 4 + 6 + 9 + + + 6 + 19 + 5 + + + + + + + do + child + + + 12 + + + 1 + 2 + 34 + + + 2 + 3 + 48 + + + 3 + 4 + 17 + + + 4 + 6 + 9 + + + 6 + 19 + 5 + + + + + + + index + do + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 3 + + + 5 + 6 + 1 + + + 7 + 8 + 1 + + + 14 + 15 + 1 + + + 31 + 32 + 1 + + + 78 + 79 + 1 + + + 112 + 113 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 3 + + + 5 + 6 + 1 + + + 7 + 8 + 1 + + + 14 + 15 + 1 + + + 31 + 32 + 1 + + + 78 + 79 + 1 + + + 112 + 113 + 1 + + + + + + + child + do + + + 12 + + + 1 + 2 + 268 + + + + + + + child + index + + + 12 + + + 1 + 2 + 268 + + + + + + + + + do_def + 117 + + + id + 117 + + + loc + 117 + + + + + id + loc + + + 12 + + + 1 + 2 + 117 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 117 + + + + + + + + + element_reference_child + 25626 + + + element_reference + 25580 + + + index + 2 + + + child + 25626 + + + + + element_reference + index + + + 12 + + + 1 + 2 + 25534 + + + 2 + 3 + 46 + + + + + + + element_reference + child + + + 12 + + + 1 + 2 + 25534 + + + 2 + 3 + 46 + + + + + + + index + element_reference + + + 12 + + + 45 + 46 + 1 + + + 24988 + 24989 + 1 + + + + + + + index + child + + + 12 + + + 45 + 46 + 1 + + + 24988 + 24989 + 1 + + + + + + + child + element_reference + + + 12 + + + 1 + 2 + 25626 + + + + + + + child + index + + + 12 + + + 1 + 2 + 25626 + + + + + + + + + element_reference_def + 25582 + + + id + 25582 + + + object + 25582 + + + loc + 25582 + + + + + id + object + + + 12 + + + 1 + 2 + 25582 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 25582 + + + + + + + object + id + + + 12 + + + 1 + 2 + 25582 + + + + + + + object + loc + + + 12 + + + 1 + 2 + 25582 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 25582 + + + + + + + loc + object + + + 12 + + + 1 + 2 + 25582 + + + + + + + + + else_child + 2696 + + + else + 2119 + + + index + 11 + + + child + 2696 + + + + + else + index + + + 12 + + + 1 + 2 + 1786 + + + 2 + 3 + 207 + + + 3 + 12 + 126 + + + + + + + else + child + + + 12 + + + 1 + 2 + 1786 + + + 2 + 3 + 207 + + + 3 + 12 + 126 + + + + + + + index + else + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 4 + 5 + 1 + + + 6 + 7 + 1 + + + 8 + 9 + 1 + + + 16 + 17 + 1 + + + 27 + 28 + 1 + + + 53 + 54 + 1 + + + 126 + 127 + 1 + + + 333 + 334 + 1 + + + 2119 + 2120 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 4 + 5 + 1 + + + 6 + 7 + 1 + + + 8 + 9 + 1 + + + 16 + 17 + 1 + + + 27 + 28 + 1 + + + 53 + 54 + 1 + + + 126 + 127 + 1 + + + 333 + 334 + 1 + + + 2119 + 2120 + 1 + + + + + + + child + else + + + 12 + + + 1 + 2 + 2696 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2696 + + + + + + + + + else_def + 2122 + + + id + 2122 + + + loc + 2122 + + + + + id + loc + + + 12 + + + 1 + 2 + 2122 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2122 + + + + + + + + + elsif_alternative + 271 + + + elsif + 271 + + + alternative + 271 + + + + + elsif + alternative + + + 12 + + + 1 + 2 + 271 + + + + + + + alternative + elsif + + + 12 + + + 1 + 2 + 271 + + + + + + + + + elsif_consequence + 491 + + + elsif + 491 + + + consequence + 491 + + + + + elsif + consequence + + + 12 + + + 1 + 2 + 491 + + + + + + + consequence + elsif + + + 12 + + + 1 + 2 + 491 + + + + + + + + + elsif_def + 491 + + + id + 491 + + + condition + 491 + + + loc + 491 + + + + + id + condition + + + 12 + + + 1 + 2 + 491 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 491 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 491 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 491 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 491 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 491 + + + + + + + + + end_block_child + 0 + + + end_block + 0 + + + index + 0 + + + child + 0 + + + + + end_block + index + + + 12 + + + + + + end_block + child + + + 12 + + + + + + index + end_block + + + 12 + + + + + + index + child + + + 12 + + + + + + child + end_block + + + 12 + + + 1 + 2 + 1 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1 + + + + + + + + + end_block_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + ensure_child + 1517 + + + ensure + 1125 + + + index + 16 + + + child + 1517 + + + + + ensure + index + + + 12 + + + 1 + 2 + 878 + + + 2 + 3 + 156 + + + 3 + 5 + 85 + + + 6 + 17 + 6 + + + + + + + ensure + child + + + 12 + + + 1 + 2 + 878 + + + 2 + 3 + 156 + + + 3 + 5 + 85 + + + 6 + 17 + 6 + + + + + + + index + ensure + + + 12 + + + 2 + 3 + 8 + + + 5 + 6 + 2 + + + 6 + 7 + 2 + + + 16 + 17 + 1 + + + 91 + 92 + 1 + + + 247 + 248 + 1 + + + 1125 + 1126 + 1 + + + + + + + index + child + + + 12 + + + 2 + 3 + 8 + + + 5 + 6 + 2 + + + 6 + 7 + 2 + + + 16 + 17 + 1 + + + 91 + 92 + 1 + + + 247 + 248 + 1 + + + 1125 + 1126 + 1 + + + + + + + child + ensure + + + 12 + + + 1 + 2 + 1517 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1517 + + + + + + + + + ensure_def + 1125 + + + id + 1125 + + + loc + 1125 + + + + + id + loc + + + 12 + + + 1 + 2 + 1125 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1125 + + + + + + + + + exception_variable_def + 309 + + + id + 309 + + + child + 309 + + + loc + 309 + + + + + id + child + + + 12 + + + 1 + 2 + 309 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 309 + + + + + + + child + id + + + 12 + + + 1 + 2 + 309 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 309 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 309 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 309 + + + + + + + + + exceptions_child + 496 + + + exceptions + 429 + + + index + 8 + + + child + 496 + + + + + exceptions + index + + + 12 + + + 1 + 2 + 386 + + + 2 + 3 + 30 + + + 3 + 9 + 12 + + + + + + + exceptions + child + + + 12 + + + 1 + 2 + 386 + + + 2 + 3 + 30 + + + 3 + 9 + 12 + + + + + + + index + exceptions + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 1 + + + 12 + 13 + 1 + + + 42 + 43 + 1 + + + 420 + 421 + 1 + + + + + + + index + child + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 1 + + + 12 + 13 + 1 + + + 42 + 43 + 1 + + + 420 + 421 + 1 + + + + + + + child + exceptions + + + 12 + + + 1 + 2 + 496 + + + + + + + child + index + + + 12 + + + 1 + 2 + 496 + + + + + + + + + exceptions_def + 429 + + + id + 429 + + + loc + 429 + + + + + id + loc + + + 12 + + + 1 + 2 + 429 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 429 + + + + + + + + + files + 6322 + + + id + 6322 + + + name + 6322 + + + simple + 4840 + + + ext + 6 + + + fromSource + 3 + + + + + id + name + + + 12 + + + 1 + 2 + 6322 + + + + + + + id + simple + + + 12 + + + 1 + 2 + 6322 + + + + + + + id + ext + + + 12 + + + 1 + 2 + 6322 + + + + + + + id + fromSource + + + 12 + + + 1 + 2 + 6322 + + + + + + + name + id + + + 12 + + + 1 + 2 + 6322 + + + + + + + name + simple + + + 12 + + + 1 + 2 + 6322 + + + + + + + name + ext + + + 12 + + + 1 + 2 + 6322 + + + + + + + name + fromSource + + + 12 + + + 1 + 2 + 6322 + + + + + + + simple + id + + + 12 + + + 1 + 2 + 4212 + + + 2 + 3 + 429 + + + 3 + 45 + 197 + + + + + + + simple + name + + + 12 + + + 1 + 2 + 4212 + + + 2 + 3 + 429 + + + 3 + 45 + 197 + + + + + + + simple + ext + + + 12 + + + 1 + 2 + 4840 + + + + + + + simple + fromSource + + + 12 + + + 1 + 2 + 4840 + + + + + + + ext + id + + + 12 + + + 429 + 430 + 3 + + + 1426 + 1427 + 3 + + + + + + + ext + name + + + 12 + + + 429 + 430 + 3 + + + 1426 + 1427 + 3 + + + + + + + ext + simple + + + 12 + + + 220 + 221 + 3 + + + 1200 + 1201 + 3 + + + + + + + ext + fromSource + + + 12 + + + 1 + 2 + 6 + + + + + + + fromSource + id + + + 12 + + + 1855 + 1856 + 3 + + + + + + + fromSource + name + + + 12 + + + 1855 + 1856 + 3 + + + + + + + fromSource + simple + + + 12 + + + 1420 + 1421 + 3 + + + + + + + fromSource + ext + + + 12 + + + 2 + 3 + 3 + + + + + + + + + folders + 1489 + + + id + 1489 + + + name + 1489 + + + simple + 630 + + + + + id + name + + + 12 + + + 1 + 2 + 1489 + + + + + + + id + simple + + + 12 + + + 1 + 2 + 1489 + + + + + + + name + id + + + 12 + + + 1 + 2 + 1489 + + + + + + + name + simple + + + 12 + + + 1 + 2 + 1489 + + + + + + + simple + id + + + 12 + + + 1 + 2 + 337 + + + 2 + 3 + 153 + + + 3 + 4 + 51 + + + 4 + 7 + 54 + + + 7 + 54 + 34 + + + + + + + simple + name + + + 12 + + + 1 + 2 + 337 + + + 2 + 3 + 153 + + + 3 + 4 + 51 + + + 4 + 7 + 54 + + + 7 + 54 + 34 + + + + + + + + + for_def + 1 + + + id + 1 + + + body + 1 + + + pattern + 1 + + + value + 1 + + + loc + 1 + + + + + id + body + + + 12 + + + 1 + 2 + 1 + + + + + + + id + pattern + + + 12 + + + 1 + 2 + 1 + + + + + + + id + value + + + 12 + + + 1 + 2 + 1 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + body + id + + + 12 + + + 1 + 2 + 1 + + + + + + + body + pattern + + + 12 + + + 1 + 2 + 1 + + + + + + + body + value + + + 12 + + + 1 + 2 + 1 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + pattern + id + + + 12 + + + 1 + 2 + 1 + + + + + + + pattern + body + + + 12 + + + 1 + 2 + 1 + + + + + + + pattern + value + + + 12 + + + 1 + 2 + 1 + + + + + + + pattern + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + value + id + + + 12 + + + 1 + 2 + 1 + + + + + + + value + body + + + 12 + + + 1 + 2 + 1 + + + + + + + value + pattern + + + 12 + + + 1 + 2 + 1 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + pattern + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 1 + + + + + + + + + hash_child + 14706 + + + hash + 6654 + + + index + 113 + + + child + 14706 + + + + + hash + index + + + 12 + + + 1 + 2 + 3375 + + + 2 + 3 + 1747 + + + 3 + 4 + 688 + + + 4 + 6 + 516 + + + 6 + 112 + 325 + + + + + + + hash + child + + + 12 + + + 1 + 2 + 3375 + + + 2 + 3 + 1747 + + + 3 + 4 + 688 + + + 4 + 6 + 516 + + + 6 + 112 + 325 + + + + + + + index + hash + + + 12 + + + 1 + 2 + 42 + + + 2 + 3 + 20 + + + 3 + 10 + 4 + + + 10 + 11 + 9 + + + 11 + 23 + 9 + + + 23 + 32 + 8 + + + 34 + 70 + 9 + + + 82 + 3204 + 9 + + + 6500 + 6501 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 42 + + + 2 + 3 + 20 + + + 3 + 10 + 4 + + + 10 + 11 + 9 + + + 11 + 23 + 9 + + + 23 + 32 + 8 + + + 34 + 70 + 9 + + + 82 + 3204 + 9 + + + 6500 + 6501 + 1 + + + + + + + child + hash + + + 12 + + + 1 + 2 + 14706 + + + + + + + child + index + + + 12 + + + 1 + 2 + 14706 + + + + + + + + + hash_def + 8122 + + + id + 8122 + + + loc + 8122 + + + + + id + loc + + + 12 + + + 1 + 2 + 8122 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 8122 + + + + + + + + + hash_splat_argument_def + 398 + + + id + 398 + + + child + 398 + + + loc + 398 + + + + + id + child + + + 12 + + + 1 + 2 + 398 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 398 + + + + + + + child + id + + + 12 + + + 1 + 2 + 398 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 398 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 398 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 398 + + + + + + + + + hash_splat_parameter_def + 413 + + + id + 413 + + + loc + 413 + + + + + id + loc + + + 12 + + + 1 + 2 + 413 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 413 + + + + + + + + + hash_splat_parameter_name + 350 + + + hash_splat_parameter + 350 + + + name + 350 + + + + + hash_splat_parameter + name + + + 12 + + + 1 + 2 + 350 + + + + + + + name + hash_splat_parameter + + + 12 + + + 1 + 2 + 350 + + + + + + + + + heredoc_body_child + 7402 + + + heredoc_body + 1610 + + + index + 73 + + + child + 7402 + + + + + heredoc_body + index + + + 12 + + + 2 + 3 + 903 + + + 4 + 5 + 187 + + + 5 + 6 + 1 + + + 6 + 7 + 247 + + + 7 + 9 + 94 + + + 10 + 15 + 124 + + + 16 + 73 + 51 + + + + + + + heredoc_body + child + + + 12 + + + 2 + 3 + 903 + + + 4 + 5 + 187 + + + 5 + 6 + 1 + + + 6 + 7 + 247 + + + 7 + 9 + 94 + + + 10 + 15 + 124 + + + 16 + 73 + 51 + + + + + + + index + heredoc_body + + + 12 + + + 1 + 2 + 22 + + + 2 + 3 + 6 + + + 3 + 4 + 4 + + + 4 + 7 + 5 + + + 7 + 8 + 3 + + + 9 + 12 + 6 + + + 12 + 22 + 6 + + + 24 + 51 + 6 + + + 77 + 173 + 6 + + + 260 + 691 + 6 + + + 1573 + 1574 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 22 + + + 2 + 3 + 6 + + + 3 + 4 + 4 + + + 4 + 7 + 5 + + + 7 + 8 + 3 + + + 9 + 12 + 6 + + + 12 + 22 + 6 + + + 24 + 51 + 6 + + + 77 + 173 + 6 + + + 260 + 691 + 6 + + + 1573 + 1574 + 2 + + + + + + + child + heredoc_body + + + 12 + + + 1 + 2 + 7402 + + + + + + + child + index + + + 12 + + + 1 + 2 + 7402 + + + + + + + + + heredoc_body_def + 1610 + + + id + 1610 + + + loc + 1610 + + + + + id + loc + + + 12 + + + 1 + 2 + 1610 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1610 + + + + + + + + + if_alternative + 1962 + + + if + 1962 + + + alternative + 1962 + + + + + if + alternative + + + 12 + + + 1 + 2 + 1962 + + + + + + + alternative + if + + + 12 + + + 1 + 2 + 1962 + + + + + + + + + if_consequence + 5694 + + + if + 5694 + + + consequence + 5694 + + + + + if + consequence + + + 12 + + + 1 + 2 + 5694 + + + + + + + consequence + if + + + 12 + + + 1 + 2 + 5694 + + + + + + + + + if_def + 5713 + + + id + 5713 + + + condition + 5713 + + + loc + 5713 + + + + + id + condition + + + 12 + + + 1 + 2 + 5713 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 5713 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 5713 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 5713 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 5713 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 5713 + + + + + + + + + if_modifier_def + 4276 + + + id + 4276 + + + body + 4276 + + + condition + 4276 + + + loc + 4276 + + + + + id + body + + + 12 + + + 1 + 2 + 4276 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 4276 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4276 + + + + + + + body + id + + + 12 + + + 1 + 2 + 4276 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 4276 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 4276 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 4276 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 4276 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 4276 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4276 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 4276 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 4276 + + + + + + + + + in_def + 1 + + + id + 1 + + + child + 1 + + + loc + 1 + + + + + id + child + + + 12 + + + 1 + 2 + 1 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + child + id + + + 12 + + + 1 + 2 + 1 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 1 + + + + + + + + + interpolation_child + 11773 + + + interpolation + 11773 + + + index + 1 + + + child + 11773 + + + + + interpolation + index + + + 12 + + + 1 + 2 + 11773 + + + + + + + interpolation + child + + + 12 + + + 1 + 2 + 11773 + + + + + + + index + interpolation + + + 12 + + + 11501 + 11502 + 1 + + + + + + + index + child + + + 12 + + + 11501 + 11502 + 1 + + + + + + + child + interpolation + + + 12 + + + 1 + 2 + 11773 + + + + + + + child + index + + + 12 + + + 1 + 2 + 11773 + + + + + + + + + interpolation_def + 11773 + + + id + 11773 + + + loc + 11773 + + + + + id + loc + + + 12 + + + 1 + 2 + 11773 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 11773 + + + + + + + + + keyword_parameter_def + 1099 + + + id + 1099 + + + name + 1099 + + + loc + 1099 + + + + + id + name + + + 12 + + + 1 + 2 + 1099 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1099 + + + + + + + name + id + + + 12 + + + 1 + 2 + 1099 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 1099 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1099 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 1099 + + + + + + + + + keyword_parameter_value + 812 + + + keyword_parameter + 812 + + + value + 812 + + + + + keyword_parameter + value + + + 12 + + + 1 + 2 + 812 + + + + + + + value + keyword_parameter + + + 12 + + + 1 + 2 + 812 + + + + + + + + + lambda_def + 660 + + + id + 660 + + + body + 660 + + + loc + 660 + + + + + id + body + + + 12 + + + 1 + 2 + 660 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 660 + + + + + + + body + id + + + 12 + + + 1 + 2 + 660 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 660 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 660 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 660 + + + + + + + + + lambda_parameters + 190 + + + lambda + 190 + + + parameters + 190 + + + + + lambda + parameters + + + 12 + + + 1 + 2 + 190 + + + + + + + parameters + lambda + + + 12 + + + 1 + 2 + 190 + + + + + + + + + lambda_parameters_child + 243 + + + lambda_parameters + 185 + + + index + 4 + + + child + 243 + + + + + lambda_parameters + index + + + 12 + + + 1 + 2 + 147 + + + 2 + 3 + 24 + + + 3 + 5 + 14 + + + + + + + lambda_parameters + child + + + 12 + + + 1 + 2 + 147 + + + 2 + 3 + 24 + + + 3 + 5 + 14 + + + + + + + index + lambda_parameters + + + 12 + + + 6 + 7 + 1 + + + 14 + 15 + 1 + + + 38 + 39 + 1 + + + 185 + 186 + 1 + + + + + + + index + child + + + 12 + + + 6 + 7 + 1 + + + 14 + 15 + 1 + + + 38 + 39 + 1 + + + 185 + 186 + 1 + + + + + + + child + lambda_parameters + + + 12 + + + 1 + 2 + 243 + + + + + + + child + index + + + 12 + + + 1 + 2 + 243 + + + + + + + + + lambda_parameters_def + 190 + + + id + 190 + + + loc + 190 + + + + + id + loc + + + 12 + + + 1 + 2 + 190 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 190 + + + + + + + + + left_assignment_list_child + 1721 + + + left_assignment_list + 773 + + + index + 8 + + + child + 1721 + + + + + left_assignment_list + index + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 632 + + + 3 + 4 + 118 + + + 4 + 9 + 21 + + + + + + + left_assignment_list + child + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 632 + + + 3 + 4 + 118 + + + 4 + 9 + 21 + + + + + + + index + left_assignment_list + + + 12 + + + 2 + 3 + 1 + + + 3 + 4 + 2 + + + 9 + 10 + 1 + + + 21 + 22 + 1 + + + 139 + 140 + 1 + + + 771 + 772 + 1 + + + 773 + 774 + 1 + + + + + + + index + child + + + 12 + + + 2 + 3 + 1 + + + 3 + 4 + 2 + + + 9 + 10 + 1 + + + 21 + 22 + 1 + + + 139 + 140 + 1 + + + 771 + 772 + 1 + + + 773 + 774 + 1 + + + + + + + child + left_assignment_list + + + 12 + + + 1 + 2 + 1721 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1721 + + + + + + + + + left_assignment_list_def + 773 + + + id + 773 + + + loc + 773 + + + + + id + loc + + + 12 + + + 1 + 2 + 773 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 773 + + + + + + + + + locations_default + 2604972 + + + id + 2604972 + + + file + 6322 + + + start_line + 4526 + + + start_column + 1060 + + + end_line + 4526 + + + end_column + 1070 + + + + + id + file + + + 12 + + + 1 + 2 + 2604972 + + + + + + + id + start_line + + + 12 + + + 1 + 2 + 2604972 + + + + + + + id + start_column + + + 12 + + + 1 + 2 + 2604972 + + + + + + + id + end_line + + + 12 + + + 1 + 2 + 2604972 + + + + + + + id + end_column + + + 12 + + + 1 + 2 + 2604972 + + + + + + + file + id + + + 12 + + + 1 + 28 + 487 + + + 28 + 42 + 484 + + + 42 + 63 + 477 + + + 63 + 83 + 477 + + + 83 + 100 + 477 + + + 100 + 137 + 480 + + + 137 + 181 + 497 + + + 181 + 228 + 477 + + + 228 + 310 + 484 + + + 311 + 416 + 480 + + + 416 + 593 + 480 + + + 593 + 1000 + 477 + + + 1002 + 4862 + 477 + + + 4991 + 11002 + 64 + + + + + + + file + start_line + + + 12 + + + 1 + 5 + 368 + + + 5 + 6 + 460 + + + 6 + 8 + 412 + + + 8 + 10 + 507 + + + 10 + 13 + 572 + + + 13 + 17 + 555 + + + 17 + 22 + 559 + + + 22 + 29 + 524 + + + 29 + 39 + 538 + + + 39 + 52 + 477 + + + 52 + 76 + 480 + + + 76 + 150 + 477 + + + 150 + 1135 + 388 + + + + + + + file + start_column + + + 12 + + + 1 + 16 + 487 + + + 16 + 22 + 528 + + + 22 + 30 + 501 + + + 30 + 38 + 524 + + + 38 + 44 + 490 + + + 44 + 51 + 480 + + + 51 + 59 + 494 + + + 59 + 67 + 490 + + + 67 + 74 + 497 + + + 74 + 84 + 507 + + + 84 + 95 + 490 + + + 95 + 114 + 477 + + + 114 + 241 + 351 + + + + + + + file + end_line + + + 12 + + + 1 + 5 + 368 + + + 5 + 6 + 449 + + + 6 + 8 + 422 + + + 8 + 10 + 507 + + + 10 + 13 + 572 + + + 13 + 17 + 555 + + + 17 + 22 + 559 + + + 22 + 29 + 524 + + + 29 + 39 + 538 + + + 39 + 52 + 477 + + + 52 + 76 + 480 + + + 76 + 150 + 477 + + + 150 + 1135 + 388 + + + + + + + file + end_column + + + 12 + + + 1 + 19 + 576 + + + 19 + 26 + 484 + + + 26 + 34 + 501 + + + 34 + 42 + 490 + + + 42 + 48 + 477 + + + 48 + 57 + 521 + + + 57 + 64 + 480 + + + 64 + 72 + 511 + + + 72 + 79 + 480 + + + 79 + 89 + 511 + + + 89 + 101 + 511 + + + 101 + 121 + 497 + + + 121 + 246 + 279 + + + + + + + start_line + id + + + 12 + + + 1 + 2 + 371 + + + 2 + 8 + 163 + + + 8 + 11 + 344 + + + 11 + 23 + 344 + + + 23 + 39 + 344 + + + 39 + 77 + 344 + + + 77 + 114 + 340 + + + 114 + 154 + 340 + + + 154 + 208 + 340 + + + 208 + 287 + 344 + + + 287 + 482 + 344 + + + 485 + 1012 + 340 + + + 1015 + 2889 + 340 + + + 2913 + 14685 + 221 + + + + + + + start_line + file + + + 12 + + + 1 + 2 + 1250 + + + 2 + 5 + 412 + + + 5 + 11 + 398 + + + 11 + 14 + 310 + + + 14 + 18 + 368 + + + 18 + 25 + 398 + + + 25 + 39 + 340 + + + 39 + 78 + 340 + + + 78 + 173 + 344 + + + 178 + 1184 + 340 + + + 1187 + 1680 + 20 + + + + + + + start_line + start_column + + + 12 + + + 1 + 2 + 374 + + + 2 + 5 + 146 + + + 5 + 8 + 357 + + + 8 + 16 + 398 + + + 16 + 25 + 340 + + + 25 + 39 + 347 + + + 39 + 49 + 357 + + + 49 + 59 + 351 + + + 59 + 71 + 351 + + + 71 + 80 + 354 + + + 80 + 93 + 354 + + + 93 + 114 + 340 + + + 114 + 163 + 344 + + + 163 + 220 + 105 + + + + + + + start_line + end_line + + + 12 + + + 1 + 2 + 1135 + + + 2 + 3 + 743 + + + 3 + 4 + 412 + + + 4 + 5 + 327 + + + 5 + 6 + 361 + + + 6 + 7 + 279 + + + 7 + 10 + 388 + + + 10 + 15 + 381 + + + 15 + 25 + 357 + + + 25 + 260 + 139 + + + + + + + start_line + end_column + + + 12 + + + 1 + 2 + 374 + + + 2 + 6 + 170 + + + 6 + 9 + 385 + + + 9 + 17 + 391 + + + 17 + 28 + 340 + + + 28 + 43 + 340 + + + 43 + 53 + 361 + + + 53 + 64 + 364 + + + 64 + 75 + 347 + + + 75 + 84 + 340 + + + 84 + 97 + 340 + + + 97 + 120 + 347 + + + 120 + 174 + 351 + + + 175 + 222 + 68 + + + + + + + start_column + id + + + 12 + + + 1 + 2 + 81 + + + 2 + 4 + 92 + + + 4 + 10 + 88 + + + 10 + 21 + 81 + + + 21 + 34 + 81 + + + 36 + 62 + 81 + + + 63 + 116 + 81 + + + 130 + 342 + 81 + + + 369 + 1166 + 81 + + + 1182 + 2945 + 81 + + + 3082 + 7421 + 81 + + + 7778 + 11207 + 81 + + + 11280 + 41035 + 61 + + + + + + + start_column + file + + + 12 + + + 1 + 2 + 126 + + + 2 + 4 + 85 + + + 4 + 9 + 81 + + + 9 + 17 + 88 + + + 17 + 27 + 81 + + + 27 + 55 + 85 + + + 56 + 125 + 81 + + + 125 + 305 + 81 + + + 317 + 585 + 81 + + + 598 + 998 + 81 + + + 1042 + 1223 + 81 + + + 1227 + 1315 + 85 + + + 1357 + 1550 + 17 + + + + + + + start_column + start_line + + + 12 + + + 1 + 2 + 112 + + + 2 + 4 + 85 + + + 4 + 9 + 81 + + + 9 + 17 + 85 + + + 17 + 27 + 78 + + + 27 + 44 + 81 + + + 44 + 85 + 81 + + + 85 + 177 + 81 + + + 186 + 351 + 81 + + + 354 + 553 + 81 + + + 553 + 759 + 81 + + + 760 + 810 + 81 + + + 810 + 1028 + 44 + + + + + + + start_column + end_line + + + 12 + + + 1 + 2 + 109 + + + 2 + 4 + 85 + + + 4 + 9 + 85 + + + 9 + 17 + 85 + + + 17 + 28 + 95 + + + 28 + 53 + 81 + + + 57 + 103 + 81 + + + 109 + 204 + 81 + + + 204 + 389 + 81 + + + 426 + 609 + 81 + + + 619 + 786 + 81 + + + 786 + 846 + 81 + + + 877 + 1028 + 27 + + + + + + + start_column + end_column + + + 12 + + + 1 + 2 + 109 + + + 2 + 3 + 68 + + + 3 + 5 + 81 + + + 5 + 9 + 81 + + + 9 + 12 + 85 + + + 12 + 19 + 95 + + + 19 + 27 + 88 + + + 27 + 43 + 81 + + + 43 + 66 + 81 + + + 66 + 89 + 85 + + + 90 + 121 + 85 + + + 126 + 149 + 81 + + + 149 + 187 + 34 + + + + + + + end_line + id + + + 12 + + + 1 + 3 + 102 + + + 3 + 4 + 340 + + + 4 + 8 + 344 + + + 8 + 21 + 374 + + + 21 + 35 + 340 + + + 35 + 71 + 340 + + + 71 + 106 + 340 + + + 106 + 146 + 340 + + + 146 + 202 + 340 + + + 202 + 264 + 344 + + + 264 + 433 + 340 + + + 436 + 889 + 340 + + + 891 + 2113 + 340 + + + 2173 + 13376 + 293 + + + + + + + end_line + file + + + 12 + + + 1 + 2 + 1250 + + + 2 + 5 + 412 + + + 5 + 11 + 398 + + + 11 + 14 + 310 + + + 14 + 18 + 368 + + + 18 + 25 + 398 + + + 25 + 39 + 340 + + + 39 + 78 + 340 + + + 78 + 173 + 344 + + + 178 + 1184 + 340 + + + 1188 + 1680 + 20 + + + + + + + end_line + start_line + + + 12 + + + 1 + 2 + 1083 + + + 2 + 3 + 698 + + + 3 + 4 + 419 + + + 4 + 5 + 351 + + + 5 + 6 + 354 + + + 6 + 7 + 224 + + + 7 + 10 + 415 + + + 10 + 16 + 391 + + + 16 + 25 + 340 + + + 25 + 36 + 245 + + + + + + + end_line + start_column + + + 12 + + + 1 + 2 + 27 + + + 2 + 3 + 419 + + + 3 + 6 + 340 + + + 6 + 14 + 340 + + + 14 + 21 + 351 + + + 21 + 36 + 351 + + + 36 + 47 + 357 + + + 47 + 57 + 361 + + + 57 + 68 + 347 + + + 68 + 77 + 347 + + + 77 + 89 + 368 + + + 89 + 106 + 340 + + + 106 + 137 + 344 + + + 138 + 220 + 228 + + + + + + + end_line + end_column + + + 12 + + + 1 + 2 + 371 + + + 2 + 5 + 149 + + + 5 + 8 + 354 + + + 8 + 16 + 391 + + + 16 + 26 + 351 + + + 26 + 41 + 361 + + + 41 + 52 + 368 + + + 52 + 63 + 368 + + + 63 + 74 + 351 + + + 74 + 84 + 354 + + + 84 + 98 + 361 + + + 98 + 122 + 368 + + + 122 + 189 + 344 + + + 191 + 225 + 30 + + + + + + + end_column + id + + + 12 + + + 1 + 2 + 57 + + + 2 + 5 + 85 + + + 5 + 10 + 85 + + + 10 + 22 + 81 + + + 22 + 33 + 81 + + + 33 + 64 + 85 + + + 64 + 130 + 81 + + + 131 + 339 + 81 + + + 356 + 1222 + 81 + + + 1283 + 2975 + 81 + + + 3087 + 6873 + 81 + + + 7069 + 10622 + 81 + + + 10676 + 12015 + 81 + + + 12205 + 18687 + 20 + + + + + + + end_column + file + + + 12 + + + 1 + 2 + 109 + + + 2 + 4 + 78 + + + 4 + 8 + 85 + + + 8 + 15 + 85 + + + 15 + 25 + 85 + + + 25 + 42 + 81 + + + 42 + 90 + 81 + + + 92 + 222 + 81 + + + 235 + 503 + 81 + + + 558 + 952 + 81 + + + 959 + 1224 + 81 + + + 1233 + 1313 + 81 + + + 1314 + 1577 + 54 + + + + + + + end_column + start_line + + + 12 + + + 1 + 2 + 98 + + + 2 + 4 + 81 + + + 4 + 8 + 92 + + + 8 + 16 + 85 + + + 16 + 26 + 85 + + + 26 + 46 + 88 + + + 46 + 92 + 85 + + + 94 + 194 + 81 + + + 204 + 364 + 81 + + + 384 + 582 + 81 + + + 586 + 789 + 81 + + + 789 + 827 + 81 + + + 827 + 949 + 44 + + + + + + + end_column + start_column + + + 12 + + + 1 + 2 + 64 + + + 2 + 5 + 98 + + + 5 + 9 + 92 + + + 9 + 14 + 88 + + + 14 + 23 + 95 + + + 23 + 38 + 85 + + + 38 + 47 + 81 + + + 47 + 56 + 88 + + + 57 + 68 + 81 + + + 69 + 80 + 81 + + + 80 + 91 + 88 + + + 91 + 100 + 81 + + + 100 + 111 + 40 + + + + + + + end_column + end_line + + + 12 + + + 1 + 2 + 102 + + + 2 + 4 + 81 + + + 4 + 8 + 88 + + + 8 + 16 + 88 + + + 16 + 26 + 85 + + + 26 + 46 + 85 + + + 46 + 91 + 85 + + + 92 + 205 + 81 + + + 206 + 363 + 81 + + + 381 + 569 + 81 + + + 581 + 789 + 81 + + + 789 + 827 + 81 + + + 827 + 906 + 44 + + + + + + + + + method_child + 81847 + + + method + 30153 + + + index + 76 + + + child + 81847 + + + + + method + index + + + 12 + + + 1 + 2 + 13610 + + + 2 + 3 + 5547 + + + 3 + 4 + 3895 + + + 4 + 5 + 2405 + + + 5 + 7 + 2533 + + + 7 + 77 + 2163 + + + + + + + method + child + + + 12 + + + 1 + 2 + 13610 + + + 2 + 3 + 5547 + + + 3 + 4 + 3895 + + + 4 + 5 + 2405 + + + 5 + 7 + 2533 + + + 7 + 77 + 2163 + + + + + + + index + method + + + 12 + + + 1 + 2 + 7 + + + 2 + 4 + 2 + + + 4 + 5 + 9 + + + 5 + 6 + 10 + + + 6 + 7 + 7 + + + 7 + 12 + 5 + + + 13 + 19 + 6 + + + 20 + 36 + 6 + + + 43 + 114 + 6 + + + 148 + 401 + 6 + + + 501 + 2164 + 6 + + + 3155 + 30154 + 6 + + + + + + + index + child + + + 12 + + + 1 + 2 + 7 + + + 2 + 4 + 2 + + + 4 + 5 + 9 + + + 5 + 6 + 10 + + + 6 + 7 + 7 + + + 7 + 12 + 5 + + + 13 + 19 + 6 + + + 20 + 36 + 6 + + + 43 + 114 + 6 + + + 148 + 401 + 6 + + + 501 + 2164 + 6 + + + 3155 + 30154 + 6 + + + + + + + child + method + + + 12 + + + 1 + 2 + 81847 + + + + + + + child + index + + + 12 + + + 1 + 2 + 81847 + + + + + + + + + method_def + 30455 + + + id + 30455 + + + name + 30455 + + + loc + 30455 + + + + + id + name + + + 12 + + + 1 + 2 + 30455 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 30455 + + + + + + + name + id + + + 12 + + + 1 + 2 + 30455 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 30455 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 30455 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 30455 + + + + + + + + + method_parameters + 8396 + + + method + 8396 + + + parameters + 8396 + + + + + method + parameters + + + 12 + + + 1 + 2 + 8396 + + + + + + + parameters + method + + + 12 + + + 1 + 2 + 8396 + + + + + + + + + method_parameters_child + 14612 + + + method_parameters + 8834 + + + index + 11 + + + child + 14612 + + + + + method_parameters + index + + + 12 + + + 1 + 2 + 5307 + + + 2 + 3 + 2176 + + + 3 + 4 + 851 + + + 4 + 12 + 500 + + + + + + + method_parameters + child + + + 12 + + + 1 + 2 + 5307 + + + 2 + 3 + 2176 + + + 3 + 4 + 851 + + + 4 + 12 + 500 + + + + + + + index + method_parameters + + + 12 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 25 + 26 + 1 + + + 44 + 45 + 1 + + + 96 + 97 + 1 + + + 218 + 219 + 1 + + + 500 + 501 + 1 + + + 1351 + 1352 + 1 + + + 3527 + 3528 + 1 + + + 8834 + 8835 + 1 + + + + + + + index + child + + + 12 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 25 + 26 + 1 + + + 44 + 45 + 1 + + + 96 + 97 + 1 + + + 218 + 219 + 1 + + + 500 + 501 + 1 + + + 1351 + 1352 + 1 + + + 3527 + 3528 + 1 + + + 8834 + 8835 + 1 + + + + + + + child + method_parameters + + + 12 + + + 1 + 2 + 14612 + + + + + + + child + index + + + 12 + + + 1 + 2 + 14612 + + + + + + + + + method_parameters_def + 8920 + + + id + 8920 + + + loc + 8920 + + + + + id + loc + + + 12 + + + 1 + 2 + 8920 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 8920 + + + + + + + + + module_child + 9655 + + + module + 3290 + + + index + 119 + + + child + 9655 + + + + + module + index + + + 12 + + + 1 + 2 + 2339 + + + 2 + 3 + 275 + + + 3 + 5 + 237 + + + 5 + 11 + 259 + + + 11 + 120 + 180 + + + + + + + module + child + + + 12 + + + 1 + 2 + 2339 + + + 2 + 3 + 275 + + + 3 + 5 + 237 + + + 5 + 11 + 259 + + + 11 + 120 + 180 + + + + + + + index + module + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 9 + + + 3 + 4 + 25 + + + 4 + 6 + 11 + + + 6 + 9 + 9 + + + 9 + 14 + 9 + + + 15 + 23 + 10 + + + 23 + 35 + 9 + + + 35 + 73 + 9 + + + 79 + 166 + 9 + + + 180 + 677 + 9 + + + 951 + 3291 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 9 + + + 3 + 4 + 25 + + + 4 + 6 + 11 + + + 6 + 9 + 9 + + + 9 + 14 + 9 + + + 15 + 23 + 10 + + + 23 + 35 + 9 + + + 35 + 73 + 9 + + + 79 + 166 + 9 + + + 180 + 677 + 9 + + + 951 + 3291 + 2 + + + + + + + child + module + + + 12 + + + 1 + 2 + 9655 + + + + + + + child + index + + + 12 + + + 1 + 2 + 9655 + + + + + + + + + module_def + 4441 + + + id + 4441 + + + name + 4441 + + + loc + 4441 + + + + + id + name + + + 12 + + + 1 + 2 + 4441 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4441 + + + + + + + name + id + + + 12 + + + 1 + 2 + 4441 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 4441 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4441 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 4441 + + + + + + + + + next_child + 16 + + + next + 16 + + + child + 16 + + + + + next + child + + + 12 + + + 1 + 2 + 16 + + + + + + + child + next + + + 12 + + + 1 + 2 + 16 + + + + + + + + + next_def + 653 + + + id + 653 + + + loc + 653 + + + + + id + loc + + + 12 + + + 1 + 2 + 653 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 653 + + + + + + + + + numlines + 0 + + + element_id + 0 + + + num_lines + 0 + + + num_code + 0 + + + num_comment + 0 + + + + + element_id + num_lines + + + 12 + + + + + + element_id + num_code + + + 12 + + + + + + element_id + num_comment + + + 12 + + + + + + num_lines + element_id + + + 12 + + + + + + num_lines + num_code + + + 12 + + + + + + num_lines + num_comment + + + 12 + + + + + + num_code + element_id + + + 12 + + + + + + num_code + num_lines + + + 12 + + + + + + num_code + num_comment + + + 12 + + + + + + num_comment + element_id + + + 12 + + + + + + num_comment + num_lines + + + 12 + + + + + + num_comment + num_code + + + 12 + + + + + + + + operator_assignment_def + 2002 + + + id + 2002 + + + left + 2002 + + + operator + 6 + + + right + 2002 + + + loc + 2002 + + + + + id + left + + + 12 + + + 1 + 2 + 2002 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 2002 + + + + + + + id + right + + + 12 + + + 1 + 2 + 2002 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2002 + + + + + + + left + id + + + 12 + + + 1 + 2 + 2002 + + + + + + + left + operator + + + 12 + + + 1 + 2 + 2002 + + + + + + + left + right + + + 12 + + + 1 + 2 + 2002 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 2002 + + + + + + + operator + id + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 61 + 62 + 1 + + + 495 + 496 + 1 + + + 1385 + 1386 + 1 + + + + + + + operator + left + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 61 + 62 + 1 + + + 495 + 496 + 1 + + + 1385 + 1386 + 1 + + + + + + + operator + right + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 61 + 62 + 1 + + + 495 + 496 + 1 + + + 1385 + 1386 + 1 + + + + + + + operator + loc + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 61 + 62 + 1 + + + 495 + 496 + 1 + + + 1385 + 1386 + 1 + + + + + + + right + id + + + 12 + + + 1 + 2 + 2002 + + + + + + + right + left + + + 12 + + + 1 + 2 + 2002 + + + + + + + right + operator + + + 12 + + + 1 + 2 + 2002 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 2002 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2002 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 2002 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 2002 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 2002 + + + + + + + + + optional_parameter_def + 2047 + + + id + 2047 + + + name + 2047 + + + value + 2047 + + + loc + 2047 + + + + + id + name + + + 12 + + + 1 + 2 + 2047 + + + + + + + id + value + + + 12 + + + 1 + 2 + 2047 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2047 + + + + + + + name + id + + + 12 + + + 1 + 2 + 2047 + + + + + + + name + value + + + 12 + + + 1 + 2 + 2047 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 2047 + + + + + + + value + id + + + 12 + + + 1 + 2 + 2047 + + + + + + + value + name + + + 12 + + + 1 + 2 + 2047 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 2047 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2047 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 2047 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 2047 + + + + + + + + + pair_def + 62127 + + + id + 62127 + + + key__ + 62127 + + + value + 62127 + + + loc + 62127 + + + + + id + key__ + + + 12 + + + 1 + 2 + 62127 + + + + + + + id + value + + + 12 + + + 1 + 2 + 62127 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 62127 + + + + + + + key__ + id + + + 12 + + + 1 + 2 + 62127 + + + + + + + key__ + value + + + 12 + + + 1 + 2 + 62127 + + + + + + + key__ + loc + + + 12 + + + 1 + 2 + 62127 + + + + + + + value + id + + + 12 + + + 1 + 2 + 62127 + + + + + + + value + key__ + + + 12 + + + 1 + 2 + 62127 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 62127 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 62127 + + + + + + + loc + key__ + + + 12 + + + 1 + 2 + 62127 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 62127 + + + + + + + + + parenthesized_statements_child + 1653 + + + parenthesized_statements + 1652 + + + index + 2 + + + child + 1653 + + + + + parenthesized_statements + index + + + 12 + + + 1 + 2 + 1651 + + + 2 + 3 + 1 + + + + + + + parenthesized_statements + child + + + 12 + + + 1 + 2 + 1651 + + + 2 + 3 + 1 + + + + + + + index + parenthesized_statements + + + 12 + + + 1 + 2 + 1 + + + 1614 + 1615 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 1614 + 1615 + 1 + + + + + + + child + parenthesized_statements + + + 12 + + + 1 + 2 + 1653 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1653 + + + + + + + + + parenthesized_statements_def + 1652 + + + id + 1652 + + + loc + 1652 + + + + + id + loc + + + 12 + + + 1 + 2 + 1652 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1652 + + + + + + + + + pattern_def + 1186 + + + id + 1186 + + + child + 1186 + + + loc + 1186 + + + + + id + child + + + 12 + + + 1 + 2 + 1186 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1186 + + + + + + + child + id + + + 12 + + + 1 + 2 + 1186 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 1186 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1186 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 1186 + + + + + + + + + program_child + 14591 + + + program + 6251 + + + index + 139 + + + child + 14591 + + + + + program + index + + + 12 + + + 1 + 2 + 3289 + + + 2 + 3 + 1625 + + + 3 + 4 + 504 + + + 4 + 7 + 484 + + + 7 + 42 + 347 + + + + + + + program + child + + + 12 + + + 1 + 2 + 3289 + + + 2 + 3 + 1625 + + + 3 + 4 + 504 + + + 4 + 7 + 484 + + + 7 + 42 + 347 + + + + + + + index + program + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 6 + + + 4 + 5 + 20 + + + 5 + 8 + 10 + + + 8 + 12 + 10 + + + 12 + 20 + 10 + + + 20 + 34 + 10 + + + 41 + 58 + 10 + + + 76 + 103 + 10 + + + 135 + 245 + 10 + + + 392 + 1835 + 10 + + + + + + + index + child + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 6 + + + 4 + 5 + 20 + + + 5 + 8 + 10 + + + 8 + 12 + 10 + + + 12 + 20 + 10 + + + 20 + 34 + 10 + + + 41 + 58 + 10 + + + 76 + 103 + 10 + + + 135 + 245 + 10 + + + 392 + 1835 + 10 + + + + + + + child + program + + + 12 + + + 1 + 2 + 14591 + + + + + + + child + index + + + 12 + + + 1 + 2 + 14591 + + + + + + + + + program_def + 6322 + + + id + 6322 + + + loc + 6322 + + + + + id + loc + + + 12 + + + 1 + 2 + 6322 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 6322 + + + + + + + + + range_begin + 539 + + + range + 539 + + + begin + 539 + + + + + range + begin + + + 12 + + + 1 + 2 + 539 + + + + + + + begin + range + + + 12 + + + 1 + 2 + 539 + + + + + + + + + range_def + 546 + + + id + 546 + + + operator + 2 + + + loc + 546 + + + + + id + operator + + + 12 + + + 1 + 2 + 546 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 546 + + + + + + + operator + id + + + 12 + + + 122 + 123 + 1 + + + 424 + 425 + 1 + + + + + + + operator + loc + + + 12 + + + 122 + 123 + 1 + + + 424 + 425 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 546 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 546 + + + + + + + + + range_end + 463 + + + range + 463 + + + end + 463 + + + + + range + end + + + 12 + + + 1 + 2 + 463 + + + + + + + end + range + + + 12 + + + 1 + 2 + 463 + + + + + + + + + rational_def + 2 + + + id + 2 + + + child + 2 + + + loc + 2 + + + + + id + child + + + 12 + + + 1 + 2 + 2 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2 + + + + + + + child + id + + + 12 + + + 1 + 2 + 2 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 2 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 2 + + + + + + + + + redo_child + 0 + + + redo + 0 + + + child + 0 + + + + + redo + child + + + 12 + + + 1 + 2 + 1 + + + + + + + child + redo + + + 12 + + + 1 + 2 + 1 + + + + + + + + + redo_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + regex_child + 13433 + + + regex + 3974 + + + index + 43 + + + child + 13433 + + + + + regex + index + + + 12 + + + 1 + 2 + 2038 + + + 2 + 3 + 220 + + + 3 + 4 + 522 + + + 4 + 5 + 152 + + + 5 + 6 + 337 + + + 6 + 8 + 317 + + + 8 + 16 + 313 + + + 16 + 44 + 75 + + + + + + + regex + child + + + 12 + + + 1 + 2 + 2038 + + + 2 + 3 + 220 + + + 3 + 4 + 522 + + + 4 + 5 + 152 + + + 5 + 6 + 337 + + + 6 + 8 + 317 + + + 8 + 16 + 313 + + + 16 + 44 + 75 + + + + + + + index + regex + + + 12 + + + 2 + 3 + 4 + + + 4 + 7 + 3 + + + 7 + 11 + 3 + + + 12 + 17 + 3 + + + 17 + 18 + 2 + + + 20 + 22 + 3 + + + 23 + 26 + 2 + + + 26 + 32 + 3 + + + 33 + 41 + 3 + + + 58 + 94 + 3 + + + 104 + 163 + 3 + + + 221 + 328 + 3 + + + 388 + 706 + 3 + + + 1042 + 1717 + 3 + + + 1936 + 3975 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 4 + + + 4 + 7 + 3 + + + 7 + 11 + 3 + + + 12 + 17 + 3 + + + 17 + 18 + 2 + + + 20 + 22 + 3 + + + 23 + 26 + 2 + + + 26 + 32 + 3 + + + 33 + 41 + 3 + + + 58 + 94 + 3 + + + 104 + 163 + 3 + + + 221 + 328 + 3 + + + 388 + 706 + 3 + + + 1042 + 1717 + 3 + + + 1936 + 3975 + 2 + + + + + + + child + regex + + + 12 + + + 1 + 2 + 13433 + + + + + + + child + index + + + 12 + + + 1 + 2 + 13433 + + + + + + + + + regex_def + 3979 + + + id + 3979 + + + loc + 3979 + + + + + id + loc + + + 12 + + + 1 + 2 + 3979 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 3979 + + + + + + + + + rescue_body + 539 + + + rescue + 539 + + + body + 539 + + + + + rescue + body + + + 12 + + + 1 + 2 + 539 + + + + + + + body + rescue + + + 12 + + + 1 + 2 + 539 + + + + + + + + + rescue_def + 634 + + + id + 634 + + + loc + 634 + + + + + id + loc + + + 12 + + + 1 + 2 + 634 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 634 + + + + + + + + + rescue_exceptions + 429 + + + rescue + 429 + + + exceptions + 429 + + + + + rescue + exceptions + + + 12 + + + 1 + 2 + 429 + + + + + + + exceptions + rescue + + + 12 + + + 1 + 2 + 429 + + + + + + + + + rescue_modifier_def + 174 + + + id + 174 + + + body + 174 + + + handler + 174 + + + loc + 174 + + + + + id + body + + + 12 + + + 1 + 2 + 174 + + + + + + + id + handler + + + 12 + + + 1 + 2 + 174 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 174 + + + + + + + body + id + + + 12 + + + 1 + 2 + 174 + + + + + + + body + handler + + + 12 + + + 1 + 2 + 174 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 174 + + + + + + + handler + id + + + 12 + + + 1 + 2 + 174 + + + + + + + handler + body + + + 12 + + + 1 + 2 + 174 + + + + + + + handler + loc + + + 12 + + + 1 + 2 + 174 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 174 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 174 + + + + + + + loc + handler + + + 12 + + + 1 + 2 + 174 + + + + + + + + + rescue_variable + 309 + + + rescue + 309 + + + variable + 309 + + + + + rescue + variable + + + 12 + + + 1 + 2 + 309 + + + + + + + variable + rescue + + + 12 + + + 1 + 2 + 309 + + + + + + + + + rest_assignment_child + 7 + + + rest_assignment + 7 + + + child + 7 + + + + + rest_assignment + child + + + 12 + + + 1 + 2 + 7 + + + + + + + child + rest_assignment + + + 12 + + + 1 + 2 + 7 + + + + + + + + + rest_assignment_def + 18 + + + id + 18 + + + loc + 18 + + + + + id + loc + + + 12 + + + 1 + 2 + 18 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 18 + + + + + + + + + retry_child + 0 + + + retry + 0 + + + child + 0 + + + + + retry + child + + + 12 + + + 1 + 2 + 1 + + + + + + + child + retry + + + 12 + + + 1 + 2 + 1 + + + + + + + + + retry_def + 9 + + + id + 9 + + + loc + 9 + + + + + id + loc + + + 12 + + + 1 + 2 + 9 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 9 + + + + + + + + + return_child + 1611 + + + return + 1611 + + + child + 1611 + + + + + return + child + + + 12 + + + 1 + 2 + 1611 + + + + + + + child + return + + + 12 + + + 1 + 2 + 1611 + + + + + + + + + return_def + 2601 + + + id + 2601 + + + loc + 2601 + + + + + id + loc + + + 12 + + + 1 + 2 + 2601 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2601 + + + + + + + + + right_assignment_list_child + 880 + + + right_assignment_list + 414 + + + index + 5 + + + child + 880 + + + + + right_assignment_list + index + + + 12 + + + 2 + 3 + 374 + + + 3 + 5 + 38 + + + 5 + 6 + 2 + + + + + + + right_assignment_list + child + + + 12 + + + 2 + 3 + 374 + + + 3 + 5 + 38 + + + 5 + 6 + 2 + + + + + + + index + right_assignment_list + + + 12 + + + 2 + 3 + 1 + + + 10 + 11 + 1 + + + 40 + 41 + 1 + + + 414 + 415 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 1 + + + 10 + 11 + 1 + + + 40 + 41 + 1 + + + 414 + 415 + 2 + + + + + + + child + right_assignment_list + + + 12 + + + 1 + 2 + 880 + + + + + + + child + index + + + 12 + + + 1 + 2 + 880 + + + + + + + + + right_assignment_list_def + 414 + + + id + 414 + + + loc + 414 + + + + + id + loc + + + 12 + + + 1 + 2 + 414 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 414 + + + + + + + + + scope_resolution_def + 22918 + + + id + 22918 + + + name + 22918 + + + loc + 22918 + + + + + id + name + + + 12 + + + 1 + 2 + 22918 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 22918 + + + + + + + name + id + + + 12 + + + 1 + 2 + 22918 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 22918 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 22918 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 22918 + + + + + + + + + scope_resolution_scope + 22255 + + + scope_resolution + 22255 + + + scope + 22255 + + + + + scope_resolution + scope + + + 12 + + + 1 + 2 + 22255 + + + + + + + scope + scope_resolution + + + 12 + + + 1 + 2 + 22255 + + + + + + + + + setter_def + 186 + + + id + 186 + + + name + 186 + + + loc + 186 + + + + + id + name + + + 12 + + + 1 + 2 + 186 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 186 + + + + + + + name + id + + + 12 + + + 1 + 2 + 186 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 186 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 186 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 186 + + + + + + + + + singleton_class_child + 731 + + + singleton_class + 191 + + + index + 24 + + + child + 731 + + + + + singleton_class + index + + + 12 + + + 1 + 2 + 89 + + + 2 + 3 + 21 + + + 3 + 4 + 12 + + + 4 + 5 + 15 + + + 5 + 6 + 11 + + + 6 + 8 + 17 + + + 8 + 13 + 14 + + + 13 + 25 + 12 + + + + + + + singleton_class + child + + + 12 + + + 1 + 2 + 89 + + + 2 + 3 + 21 + + + 3 + 4 + 12 + + + 4 + 5 + 15 + + + 5 + 6 + 11 + + + 6 + 8 + 17 + + + 8 + 13 + 14 + + + 13 + 25 + 12 + + + + + + + index + singleton_class + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 3 + + + 3 + 5 + 2 + + + 6 + 8 + 2 + + + 8 + 9 + 2 + + + 12 + 18 + 2 + + + 18 + 20 + 2 + + + 22 + 27 + 2 + + + 33 + 44 + 2 + + + 54 + 70 + 2 + + + 81 + 103 + 2 + + + 191 + 192 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 3 + + + 3 + 5 + 2 + + + 6 + 8 + 2 + + + 8 + 9 + 2 + + + 12 + 18 + 2 + + + 18 + 20 + 2 + + + 22 + 27 + 2 + + + 33 + 44 + 2 + + + 54 + 70 + 2 + + + 81 + 103 + 2 + + + 191 + 192 + 1 + + + + + + + child + singleton_class + + + 12 + + + 1 + 2 + 731 + + + + + + + child + index + + + 12 + + + 1 + 2 + 731 + + + + + + + + + singleton_class_def + 191 + + + id + 191 + + + value + 191 + + + loc + 191 + + + + + id + value + + + 12 + + + 1 + 2 + 191 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 191 + + + + + + + value + id + + + 12 + + + 1 + 2 + 191 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 191 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 191 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 191 + + + + + + + + + singleton_method_child + 5021 + + + singleton_method + 2020 + + + index + 27 + + + child + 5021 + + + + + singleton_method + index + + + 12 + + + 1 + 2 + 1132 + + + 2 + 3 + 308 + + + 3 + 4 + 180 + + + 4 + 5 + 126 + + + 5 + 8 + 159 + + + 8 + 28 + 113 + + + + + + + singleton_method + child + + + 12 + + + 1 + 2 + 1132 + + + 2 + 3 + 308 + + + 3 + 4 + 180 + + + 4 + 5 + 126 + + + 5 + 8 + 159 + + + 8 + 28 + 113 + + + + + + + index + singleton_method + + + 12 + + + 3 + 4 + 2 + + + 4 + 5 + 2 + + + 6 + 7 + 4 + + + 7 + 9 + 2 + + + 11 + 16 + 2 + + + 22 + 27 + 2 + + + 30 + 37 + 2 + + + 48 + 64 + 2 + + + 87 + 112 + 2 + + + 142 + 195 + 2 + + + 267 + 392 + 2 + + + 567 + 869 + 2 + + + 1974 + 1975 + 1 + + + + + + + index + child + + + 12 + + + 3 + 4 + 2 + + + 4 + 5 + 2 + + + 6 + 7 + 4 + + + 7 + 9 + 2 + + + 11 + 16 + 2 + + + 22 + 27 + 2 + + + 30 + 37 + 2 + + + 48 + 64 + 2 + + + 87 + 112 + 2 + + + 142 + 195 + 2 + + + 267 + 392 + 2 + + + 567 + 869 + 2 + + + 1974 + 1975 + 1 + + + + + + + child + singleton_method + + + 12 + + + 1 + 2 + 5021 + + + + + + + child + index + + + 12 + + + 1 + 2 + 5021 + + + + + + + + + singleton_method_def + 2020 + + + id + 2020 + + + name + 2020 + + + object + 2020 + + + loc + 2020 + + + + + id + name + + + 12 + + + 1 + 2 + 2020 + + + + + + + id + object + + + 12 + + + 1 + 2 + 2020 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2020 + + + + + + + name + id + + + 12 + + + 1 + 2 + 2020 + + + + + + + name + object + + + 12 + + + 1 + 2 + 2020 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 2020 + + + + + + + object + id + + + 12 + + + 1 + 2 + 2020 + + + + + + + object + name + + + 12 + + + 1 + 2 + 2020 + + + + + + + object + loc + + + 12 + + + 1 + 2 + 2020 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2020 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 2020 + + + + + + + loc + object + + + 12 + + + 1 + 2 + 2020 + + + + + + + + + singleton_method_parameters + 1272 + + + singleton_method + 1272 + + + parameters + 1272 + + + + + singleton_method + parameters + + + 12 + + + 1 + 2 + 1272 + + + + + + + parameters + singleton_method + + + 12 + + + 1 + 2 + 1272 + + + + + + + + + sourceLocationPrefix + 3 + + + prefix + 3 + + + + + + splat_argument_def + 683 + + + id + 683 + + + child + 683 + + + loc + 683 + + + + + id + child + + + 12 + + + 1 + 2 + 683 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 683 + + + + + + + child + id + + + 12 + + + 1 + 2 + 683 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 683 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 683 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 683 + + + + + + + + + splat_parameter_def + 921 + + + id + 921 + + + loc + 921 + + + + + id + loc + + + 12 + + + 1 + 2 + 921 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 921 + + + + + + + + + splat_parameter_name + 748 + + + splat_parameter + 748 + + + name + 748 + + + + + splat_parameter + name + + + 12 + + + 1 + 2 + 748 + + + + + + + name + splat_parameter + + + 12 + + + 1 + 2 + 748 + + + + + + + + + string_array_child + 3009 + + + string_array + 932 + + + index + 88 + + + child + 3009 + + + + + string_array + index + + + 12 + + + 1 + 2 + 200 + + + 2 + 3 + 300 + + + 3 + 4 + 241 + + + 4 + 5 + 66 + + + 5 + 8 + 71 + + + 8 + 89 + 54 + + + + + + + string_array + child + + + 12 + + + 1 + 2 + 200 + + + 2 + 3 + 300 + + + 3 + 4 + 241 + + + 4 + 5 + 66 + + + 5 + 8 + 71 + + + 8 + 89 + 54 + + + + + + + index + string_array + + + 12 + + + 1 + 2 + 38 + + + 2 + 3 + 6 + + + 3 + 4 + 6 + + + 4 + 5 + 12 + + + 5 + 8 + 8 + + + 11 + 29 + 7 + + + 33 + 126 + 7 + + + 191 + 933 + 4 + + + + + + + index + child + + + 12 + + + 1 + 2 + 38 + + + 2 + 3 + 6 + + + 3 + 4 + 6 + + + 4 + 5 + 12 + + + 5 + 8 + 8 + + + 11 + 29 + 7 + + + 33 + 126 + 7 + + + 191 + 933 + 4 + + + + + + + child + string_array + + + 12 + + + 1 + 2 + 3009 + + + + + + + child + index + + + 12 + + + 1 + 2 + 3009 + + + + + + + + + string_array_def + 938 + + + id + 938 + + + loc + 938 + + + + + id + loc + + + 12 + + + 1 + 2 + 938 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 938 + + + + + + + + + string_child + 124227 + + + string__ + 90152 + + + index + 124 + + + child + 124227 + + + + + string__ + index + + + 12 + + + 1 + 2 + 83280 + + + 2 + 63 + 6791 + + + 64 + 125 + 81 + + + + + + + string__ + child + + + 12 + + + 1 + 2 + 83280 + + + 2 + 63 + 6791 + + + 64 + 125 + 81 + + + + + + + index + string__ + + + 12 + + + 1 + 19 + 4 + + + 61 + 62 + 13 + + + 62 + 63 + 37 + + + 64 + 82 + 8 + + + 142 + 144 + 10 + + + 144 + 190 + 10 + + + 190 + 206 + 10 + + + 209 + 352 + 10 + + + 381 + 465 + 10 + + + 470 + 3443 + 10 + + + 6872 + 90153 + 2 + + + + + + + index + child + + + 12 + + + 1 + 19 + 4 + + + 61 + 62 + 13 + + + 62 + 63 + 37 + + + 64 + 82 + 8 + + + 142 + 144 + 10 + + + 144 + 190 + 10 + + + 190 + 206 + 10 + + + 209 + 352 + 10 + + + 381 + 465 + 10 + + + 470 + 3443 + 10 + + + 6872 + 90153 + 2 + + + + + + + child + string__ + + + 12 + + + 1 + 2 + 124227 + + + + + + + child + index + + + 12 + + + 1 + 2 + 124227 + + + + + + + + + string_def + 91253 + + + id + 91253 + + + loc + 91253 + + + + + id + loc + + + 12 + + + 1 + 2 + 91253 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 91253 + + + + + + + + + subshell_child + 214 + + + subshell + 83 + + + index + 8 + + + child + 214 + + + + + subshell + index + + + 12 + + + 1 + 2 + 29 + + + 2 + 3 + 9 + + + 3 + 4 + 30 + + + 4 + 5 + 6 + + + 5 + 8 + 7 + + + 8 + 9 + 1 + + + + + + + subshell + child + + + 12 + + + 1 + 2 + 29 + + + 2 + 3 + 9 + + + 3 + 4 + 30 + + + 4 + 5 + 6 + + + 5 + 8 + 7 + + + 8 + 9 + 1 + + + + + + + index + subshell + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 8 + 9 + 1 + + + 14 + 15 + 1 + + + 44 + 45 + 1 + + + 53 + 54 + 1 + + + 82 + 83 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 8 + 9 + 1 + + + 14 + 15 + 1 + + + 44 + 45 + 1 + + + 53 + 54 + 1 + + + 82 + 83 + 1 + + + + + + + child + subshell + + + 12 + + + 1 + 2 + 214 + + + + + + + child + index + + + 12 + + + 1 + 2 + 214 + + + + + + + + + subshell_def + 130 + + + id + 130 + + + loc + 130 + + + + + id + loc + + + 12 + + + 1 + 2 + 130 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 130 + + + + + + + + + superclass_def + 4108 + + + id + 4108 + + + child + 4108 + + + loc + 4108 + + + + + id + child + + + 12 + + + 1 + 2 + 4108 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4108 + + + + + + + child + id + + + 12 + + + 1 + 2 + 4108 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 4108 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4108 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 4108 + + + + + + + + + symbol_array_child + 687 + + + symbol_array + 139 + + + index + 32 + + + child + 687 + + + + + symbol_array + index + + + 12 + + + 1 + 2 + 50 + + + 2 + 3 + 25 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 7 + 7 + + + 7 + 10 + 12 + + + 10 + 16 + 12 + + + 16 + 33 + 10 + + + + + + + symbol_array + child + + + 12 + + + 1 + 2 + 50 + + + 2 + 3 + 25 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 7 + 7 + + + 7 + 10 + 12 + + + 10 + 16 + 12 + + + 16 + 33 + 10 + + + + + + + index + symbol_array + + + 12 + + + 1 + 2 + 5 + + + 2 + 3 + 3 + + + 3 + 4 + 3 + + + 4 + 6 + 2 + + + 6 + 7 + 2 + + + 9 + 11 + 2 + + + 13 + 17 + 2 + + + 17 + 20 + 2 + + + 21 + 23 + 2 + + + 25 + 29 + 2 + + + 34 + 42 + 2 + + + 42 + 50 + 2 + + + 62 + 88 + 2 + + + 136 + 137 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 5 + + + 2 + 3 + 3 + + + 3 + 4 + 3 + + + 4 + 6 + 2 + + + 6 + 7 + 2 + + + 9 + 11 + 2 + + + 13 + 17 + 2 + + + 17 + 20 + 2 + + + 21 + 23 + 2 + + + 25 + 29 + 2 + + + 34 + 42 + 2 + + + 42 + 50 + 2 + + + 62 + 88 + 2 + + + 136 + 137 + 1 + + + + + + + child + symbol_array + + + 12 + + + 1 + 2 + 687 + + + + + + + child + index + + + 12 + + + 1 + 2 + 687 + + + + + + + + + symbol_array_def + 139 + + + id + 139 + + + loc + 139 + + + + + id + loc + + + 12 + + + 1 + 2 + 139 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 139 + + + + + + + + + then_child + 12972 + + + then + 7609 + + + index + 35 + + + child + 12972 + + + + + then + index + + + 12 + + + 1 + 2 + 4705 + + + 2 + 3 + 1734 + + + 3 + 4 + 641 + + + 4 + 36 + 528 + + + + + + + then + child + + + 12 + + + 1 + 2 + 4705 + + + 2 + 3 + 1734 + + + 3 + 4 + 641 + + + 4 + 36 + 528 + + + + + + + index + then + + + 12 + + + 1 + 2 + 14 + + + 3 + 5 + 2 + + + 5 + 6 + 4 + + + 7 + 10 + 3 + + + 10 + 26 + 3 + + + 42 + 86 + 3 + + + 152 + 517 + 3 + + + 1143 + 7434 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 14 + + + 3 + 5 + 2 + + + 5 + 6 + 4 + + + 7 + 10 + 3 + + + 10 + 26 + 3 + + + 42 + 86 + 3 + + + 152 + 517 + 3 + + + 1143 + 7434 + 3 + + + + + + + child + then + + + 12 + + + 1 + 2 + 12972 + + + + + + + child + index + + + 12 + + + 1 + 2 + 12972 + + + + + + + + + then_def + 7609 + + + id + 7609 + + + loc + 7609 + + + + + id + loc + + + 12 + + + 1 + 2 + 7609 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 7609 + + + + + + + + + tokeninfo + 1812992 + + + id + 1812992 + + + kind + 23 + + + file + 3633 + + + idx + 30516 + + + value + 81342 + + + loc + 1812967 + + + + + id + kind + + + 12 + + + 1 + 2 + 1812992 + + + + + + + id + file + + + 12 + + + 1 + 2 + 1812992 + + + + + + + id + idx + + + 12 + + + 1 + 2 + 1812992 + + + + + + + id + value + + + 12 + + + 1 + 2 + 1812992 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1812992 + + + + + + + kind + id + + + 12 + + + 1 + 25 + 2 + + + 106 + 182 + 2 + + + 431 + 1506 + 2 + + + 1573 + 1574 + 2 + + + 3578 + 3605 + 2 + + + 3720 + 5057 + 2 + + + 7139 + 8328 + 2 + + + 12622 + 15383 + 2 + + + 21595 + 49328 + 2 + + + 49585 + 70852 + 2 + + + 82879 + 445442 + 2 + + + 986482 + 986483 + 1 + + + + + + + kind + file + + + 12 + + + 1 + 24 + 2 + + + 24 + 81 + 2 + + + 127 + 128 + 1 + + + 448 + 449 + 3 + + + 486 + 502 + 2 + + + 794 + 904 + 2 + + + 1254 + 1305 + 2 + + + 1347 + 1610 + 2 + + + 2282 + 2363 + 2 + + + 2879 + 3394 + 2 + + + 3498 + 3531 + 2 + + + 3538 + 3539 + 1 + + + + + + + kind + idx + + + 12 + + + 1 + 25 + 2 + + + 99 + 136 + 2 + + + 331 + 422 + 2 + + + 1015 + 1027 + 2 + + + 1775 + 2075 + 2 + + + 2128 + 2308 + 2 + + + 3338 + 3381 + 2 + + + 3467 + 4192 + 2 + + + 6552 + 8526 + 2 + + + 9771 + 9941 + 2 + + + 11957 + 21770 + 2 + + + 26505 + 26506 + 1 + + + + + + + kind + value + + + 12 + + + 1 + 2 + 6 + + + 5 + 32 + 2 + + + 42 + 50 + 2 + + + 52 + 53 + 1 + + + 118 + 119 + 2 + + + 136 + 510 + 2 + + + 1614 + 2649 + 2 + + + 3380 + 4151 + 2 + + + 7019 + 8929 + 2 + + + 17094 + 38702 + 2 + + + + + + + kind + loc + + + 12 + + + 1 + 25 + 2 + + + 106 + 182 + 2 + + + 431 + 1506 + 2 + + + 1573 + 1574 + 2 + + + 3578 + 3605 + 2 + + + 3720 + 5057 + 2 + + + 7139 + 8328 + 2 + + + 12622 + 15383 + 2 + + + 21595 + 49328 + 2 + + + 49585 + 70852 + 2 + + + 82879 + 445442 + 2 + + + 986482 + 986483 + 1 + + + + + + + file + id + + + 12 + + + 1 + 21 + 327 + + + 21 + 28 + 294 + + + 28 + 34 + 294 + + + 34 + 47 + 279 + + + 47 + 63 + 277 + + + 63 + 85 + 277 + + + 85 + 128 + 275 + + + 128 + 190 + 273 + + + 190 + 287 + 274 + + + 287 + 477 + 273 + + + 477 + 824 + 273 + + + 830 + 1793 + 273 + + + 1795 + 29810 + 238 + + + + + + + file + kind + + + 12 + + + 1 + 6 + 300 + + + 6 + 7 + 600 + + + 7 + 8 + 340 + + + 8 + 9 + 560 + + + 9 + 10 + 552 + + + 10 + 11 + 387 + + + 11 + 12 + 304 + + + 12 + 14 + 327 + + + 14 + 22 + 256 + + + + + + + file + idx + + + 12 + + + 1 + 21 + 327 + + + 21 + 28 + 294 + + + 28 + 34 + 294 + + + 34 + 47 + 279 + + + 47 + 63 + 277 + + + 63 + 85 + 277 + + + 85 + 128 + 275 + + + 128 + 190 + 273 + + + 190 + 287 + 274 + + + 287 + 477 + 273 + + + 477 + 824 + 273 + + + 830 + 1793 + 273 + + + 1795 + 29810 + 238 + + + + + + + file + value + + + 12 + + + 1 + 18 + 259 + + + 18 + 21 + 318 + + + 21 + 24 + 309 + + + 24 + 29 + 322 + + + 29 + 35 + 303 + + + 35 + 42 + 294 + + + 42 + 53 + 287 + + + 53 + 67 + 284 + + + 67 + 86 + 279 + + + 86 + 121 + 277 + + + 121 + 175 + 274 + + + 175 + 328 + 273 + + + 328 + 1615 + 149 + + + + + + + file + loc + + + 12 + + + 1 + 21 + 327 + + + 21 + 28 + 294 + + + 28 + 34 + 294 + + + 34 + 47 + 279 + + + 47 + 63 + 277 + + + 63 + 85 + 277 + + + 85 + 128 + 275 + + + 128 + 190 + 273 + + + 190 + 287 + 274 + + + 287 + 477 + 273 + + + 477 + 824 + 273 + + + 830 + 1793 + 273 + + + 1795 + 29810 + 238 + + + + + + + idx + id + + + 12 + + + 1 + 2 + 6221 + + + 2 + 3 + 281 + + + 3 + 4 + 6071 + + + 4 + 5 + 2334 + + + 5 + 8 + 2773 + + + 8 + 11 + 2410 + + + 11 + 22 + 2394 + + + 22 + 42 + 2451 + + + 42 + 108 + 2292 + + + 108 + 438 + 2289 + + + 439 + 3550 + 996 + + + + + + + idx + kind + + + 12 + + + 1 + 2 + 7969 + + + 2 + 3 + 6564 + + + 3 + 4 + 4657 + + + 4 + 5 + 2483 + + + 5 + 6 + 1748 + + + 6 + 8 + 2280 + + + 8 + 12 + 2760 + + + 12 + 22 + 2050 + + + + + + + idx + file + + + 12 + + + 1 + 2 + 6221 + + + 2 + 3 + 281 + + + 3 + 4 + 6071 + + + 4 + 5 + 2334 + + + 5 + 8 + 2773 + + + 8 + 11 + 2410 + + + 11 + 22 + 2394 + + + 22 + 42 + 2451 + + + 42 + 108 + 2292 + + + 108 + 438 + 2289 + + + 439 + 3550 + 996 + + + + + + + idx + value + + + 12 + + + 1 + 2 + 6252 + + + 2 + 3 + 904 + + + 3 + 4 + 5907 + + + 4 + 5 + 2078 + + + 5 + 7 + 2404 + + + 7 + 10 + 2623 + + + 10 + 18 + 2586 + + + 18 + 31 + 2381 + + + 31 + 65 + 2313 + + + 65 + 215 + 2293 + + + 215 + 1962 + 768 + + + + + + + idx + loc + + + 12 + + + 1 + 2 + 6221 + + + 2 + 3 + 281 + + + 3 + 4 + 6071 + + + 4 + 5 + 2334 + + + 5 + 8 + 2773 + + + 8 + 11 + 2410 + + + 11 + 22 + 2394 + + + 22 + 42 + 2451 + + + 42 + 108 + 2292 + + + 108 + 438 + 2289 + + + 439 + 3550 + 996 + + + + + + + value + id + + + 12 + + + 1 + 2 + 47906 + + + 2 + 3 + 11908 + + + 3 + 4 + 5748 + + + 4 + 7 + 6839 + + + 7 + 25 + 6164 + + + 25 + 163812 + 2775 + + + + + + + value + kind + + + 12 + + + 1 + 2 + 77153 + + + 2 + 5 + 4189 + + + + + + + value + file + + + 12 + + + 1 + 2 + 62308 + + + 2 + 3 + 8151 + + + 3 + 6 + 6181 + + + 6 + 3447 + 4700 + + + + + + + value + idx + + + 12 + + + 1 + 2 + 48028 + + + 2 + 3 + 11906 + + + 3 + 4 + 5746 + + + 4 + 7 + 6815 + + + 7 + 25 + 6150 + + + 25 + 15624 + 2694 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 47907 + + + 2 + 3 + 11907 + + + 3 + 4 + 5748 + + + 4 + 7 + 6839 + + + 7 + 25 + 6165 + + + 25 + 163812 + 2774 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1812942 + + + 2 + 3 + 24 + + + + + + + loc + kind + + + 12 + + + 1 + 2 + 1812942 + + + 2 + 3 + 24 + + + + + + + loc + file + + + 12 + + + 1 + 2 + 1812967 + + + + + + + loc + idx + + + 12 + + + 1 + 2 + 1812942 + + + 2 + 3 + 24 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 1812967 + + + + + + + + + unary_def + 2445 + + + id + 2445 + + + operand + 2445 + + + operator + 5 + + + loc + 2445 + + + + + id + operand + + + 12 + + + 1 + 2 + 2445 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 2445 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2445 + + + + + + + operand + id + + + 12 + + + 1 + 2 + 2445 + + + + + + + operand + operator + + + 12 + + + 1 + 2 + 2445 + + + + + + + operand + loc + + + 12 + + + 1 + 2 + 2445 + + + + + + + operator + id + + + 12 + + + 10 + 11 + 1 + + + 60 + 61 + 1 + + + 142 + 143 + 1 + + + 533 + 534 + 1 + + + 1644 + 1645 + 1 + + + + + + + operator + operand + + + 12 + + + 10 + 11 + 1 + + + 60 + 61 + 1 + + + 142 + 143 + 1 + + + 533 + 534 + 1 + + + 1644 + 1645 + 1 + + + + + + + operator + loc + + + 12 + + + 10 + 11 + 1 + + + 60 + 61 + 1 + + + 142 + 143 + 1 + + + 533 + 534 + 1 + + + 1644 + 1645 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2445 + + + + + + + loc + operand + + + 12 + + + 1 + 2 + 2445 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 2445 + + + + + + + + + undef_child + 13 + + + undef + 13 + + + index + 1 + + + child + 13 + + + + + undef + index + + + 12 + + + 1 + 2 + 13 + + + + + + + undef + child + + + 12 + + + 1 + 2 + 13 + + + + + + + index + undef + + + 12 + + + 13 + 14 + 1 + + + + + + + index + child + + + 12 + + + 13 + 14 + 1 + + + + + + + child + undef + + + 12 + + + 1 + 2 + 13 + + + + + + + child + index + + + 12 + + + 1 + 2 + 13 + + + + + + + + + undef_def + 13 + + + id + 13 + + + loc + 13 + + + + + id + loc + + + 12 + + + 1 + 2 + 13 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 13 + + + + + + + + + unless_alternative + 11 + + + unless + 11 + + + alternative + 11 + + + + + unless + alternative + + + 12 + + + 1 + 2 + 11 + + + + + + + alternative + unless + + + 12 + + + 1 + 2 + 11 + + + + + + + + + unless_consequence + 494 + + + unless + 494 + + + consequence + 494 + + + + + unless + consequence + + + 12 + + + 1 + 2 + 494 + + + + + + + consequence + unless + + + 12 + + + 1 + 2 + 494 + + + + + + + + + unless_def + 497 + + + id + 497 + + + condition + 497 + + + loc + 497 + + + + + id + condition + + + 12 + + + 1 + 2 + 497 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 497 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 497 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 497 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 497 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 497 + + + + + + + + + unless_modifier_def + 1404 + + + id + 1404 + + + body + 1404 + + + condition + 1404 + + + loc + 1404 + + + + + id + body + + + 12 + + + 1 + 2 + 1404 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 1404 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1404 + + + + + + + body + id + + + 12 + + + 1 + 2 + 1404 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 1404 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 1404 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 1404 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 1404 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 1404 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1404 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 1404 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 1404 + + + + + + + + + until_def + 16 + + + id + 16 + + + body + 16 + + + condition + 16 + + + loc + 16 + + + + + id + body + + + 12 + + + 1 + 2 + 16 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 16 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 16 + + + + + + + body + id + + + 12 + + + 1 + 2 + 16 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 16 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 16 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 16 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 16 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 16 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 16 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 16 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 16 + + + + + + + + + until_modifier_def + 13 + + + id + 13 + + + body + 13 + + + condition + 13 + + + loc + 13 + + + + + id + body + + + 12 + + + 1 + 2 + 13 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 13 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 13 + + + + + + + body + id + + + 12 + + + 1 + 2 + 13 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 13 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 13 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 13 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 13 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 13 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 13 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 13 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 13 + + + + + + + + + when_body + 980 + + + when + 980 + + + body + 980 + + + + + when + body + + + 12 + + + 1 + 2 + 980 + + + + + + + body + when + + + 12 + + + 1 + 2 + 980 + + + + + + + + + when_def + 987 + + + id + 987 + + + loc + 987 + + + + + id + loc + + + 12 + + + 1 + 2 + 987 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 987 + + + + + + + + + when_pattern + 1186 + + + when + 987 + + + index + 14 + + + pattern + 1186 + + + + + when + index + + + 12 + + + 1 + 2 + 863 + + + 2 + 3 + 94 + + + 3 + 15 + 30 + + + + + + + when + pattern + + + 12 + + + 1 + 2 + 863 + + + 2 + 3 + 94 + + + 3 + 15 + 30 + + + + + + + index + when + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 4 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 12 + 13 + 1 + + + 30 + 31 + 1 + + + 124 + 125 + 1 + + + 987 + 988 + 1 + + + + + + + index + pattern + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 4 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 12 + 13 + 1 + + + 30 + 31 + 1 + + + 124 + 125 + 1 + + + 987 + 988 + 1 + + + + + + + pattern + when + + + 12 + + + 1 + 2 + 1186 + + + + + + + pattern + index + + + 12 + + + 1 + 2 + 1186 + + + + + + + + + while_def + 106 + + + id + 106 + + + body + 106 + + + condition + 106 + + + loc + 106 + + + + + id + body + + + 12 + + + 1 + 2 + 106 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 106 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 106 + + + + + + + body + id + + + 12 + + + 1 + 2 + 106 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 106 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 106 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 106 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 106 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 106 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 106 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 106 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 106 + + + + + + + + + while_modifier_def + 9 + + + id + 9 + + + body + 9 + + + condition + 9 + + + loc + 9 + + + + + id + body + + + 12 + + + 1 + 2 + 9 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 9 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 9 + + + + + + + body + id + + + 12 + + + 1 + 2 + 9 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 9 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 9 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 9 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 9 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 9 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 9 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 9 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 9 + + + + + + + + + yield_child + 371 + + + yield + 371 + + + child + 371 + + + + + yield + child + + + 12 + + + 1 + 2 + 371 + + + + + + + child + yield + + + 12 + + + 1 + 2 + 371 + + + + + + + + + yield_def + 841 + + + id + 841 + + + loc + 841 + + + + + id + loc + + + 12 + + + 1 + 2 + 841 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 841 + + + + + + + + + diff --git a/ql/ql/src/ql.qll b/ql/ql/src/ql.qll new file mode 100644 index 000000000000..0ff627715d94 --- /dev/null +++ b/ql/ql/src/ql.qll @@ -0,0 +1,3 @@ +import codeql.Locations +import codeql.files.FileSystem +import codeql_ql.ast.Ast diff --git a/ql/ql/src/qlpack.yml b/ql/ql/src/qlpack.yml new file mode 100644 index 000000000000..675067cdcc09 --- /dev/null +++ b/ql/ql/src/qlpack.yml @@ -0,0 +1,6 @@ +name: codeql-ql +version: 0.0.0 +dbscheme: ql.dbscheme +suites: codeql-suites +defaultSuiteFile: codeql-suites/ql-code-scanning.qls +extractor: ql diff --git a/ql/ql/src/queries/diagnostics/EmptyConsistencies.ql b/ql/ql/src/queries/diagnostics/EmptyConsistencies.ql new file mode 100644 index 000000000000..a3c36fcf5947 --- /dev/null +++ b/ql/ql/src/queries/diagnostics/EmptyConsistencies.ql @@ -0,0 +1,45 @@ +/** + * @name Consistency predicate that should be empty + * @description This query should have no results on the CodeQL repos. + * It's marked as a warning query to make the results visible. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id ql/diagnostics/empty-consitencies + */ + +import ql +import codeql_ql.ast.internal.Predicate::PredConsistency as PredConsistency +import codeql_ql.ast.internal.Type::TyConsistency as TypeConsistency +import codeql_ql.ast.internal.Builtins::BuiltinsConsistency as BuiltinsConsistency +import codeql_ql.ast.internal.Module::ModConsistency as ModConsistency +import codeql_ql.ast.internal.Variable::VarConsistency as VarConsistency +import codeql_ql.ast.internal.AstNodes::AstConsistency as AstConsistency + +from AstNode node, string msg +where + PredConsistency::noResolveCall(node) and msg = "PredConsistency::noResolveCall" + or + PredConsistency::noResolvePredicateExpr(node) and msg = "PredConsistency::noResolvePredicateExpr" + or + TypeConsistency::exprNoType(node) and msg = "TypeConsistency::exprNoType" + or + TypeConsistency::varDefNoType(node) and msg = "TypeConsistency::varDefNoType" + or + TypeConsistency::multiplePrimitives(node, _, _) and msg = "TypeConsistency::multiplePrimitives" + or + TypeConsistency::multiplePrimitivesExpr(node, _, _) and + msg = "TypeConsistency::multiplePrimitivesExpr" + or + AstConsistency::nonTotalGetParent(node) and msg = "AstConsistency::nonTotalGetParent" + or + TypeConsistency::noResolve(node) and msg = "TypeConsistency::noResolve" + or + ModConsistency::noResolve(node) and msg = "ModConsistency::noResolve" + or + ModConsistency::noResolveModuleExpr(node) and msg = "ModConsistency::noResolveModuleExpr" + or + VarConsistency::noFieldDef(node) and msg = "VarConsistency::noFieldDef" + or + VarConsistency::noVarDef(node) and msg = "VarConsistency::noVarDef" +select node, msg diff --git a/ql/ql/src/queries/diagnostics/ExtractionErrors.ql b/ql/ql/src/queries/diagnostics/ExtractionErrors.ql new file mode 100644 index 000000000000..a6efd4227430 --- /dev/null +++ b/ql/ql/src/queries/diagnostics/ExtractionErrors.ql @@ -0,0 +1,18 @@ +/** + * @name Extraction errors + * @description List all extraction errors for files in the source code directory. + * @kind diagnostic + * @id ql/diagnostics/extraction-errors + */ + +import ql +import codeql_ql.Diagnostics + +/** Gets the SARIF severity to associate an error. */ +int getSeverity() { result = 2 } + +from ExtractionError error, File f +where + f = error.getLocation().getFile() and + exists(f.getRelativePath()) +select error, "Extraction failed in " + f + " with error " + error.getMessage(), getSeverity() diff --git a/ql/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql b/ql/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql new file mode 100644 index 000000000000..de254b985989 --- /dev/null +++ b/ql/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql @@ -0,0 +1,16 @@ +/** + * @name Successfully extracted files + * @description Lists all files in the source code directory that were extracted + * without encountering an error in the file. + * @kind diagnostic + * @id ql/diagnostics/successfully-extracted-files + */ + +import ql +import codeql_ql.Diagnostics + +from File f +where + not exists(ExtractionError e | e.getLocation().getFile() = f) and + exists(f.getRelativePath()) +select f, "" diff --git a/ql/ql/src/queries/metrics/FLines.ql b/ql/ql/src/queries/metrics/FLines.ql new file mode 100644 index 000000000000..d3d84455f644 --- /dev/null +++ b/ql/ql/src/queries/metrics/FLines.ql @@ -0,0 +1,13 @@ +/** + * @name Number of lines + * @kind metric + * @description The number of lines in each file. + * @metricType file + * @id ql/lines-per-file + */ + +import ql + +from File f, int n +where n = f.getNumberOfLines() +select f, n order by n desc diff --git a/ql/ql/src/queries/metrics/FLinesOfCode.ql b/ql/ql/src/queries/metrics/FLinesOfCode.ql new file mode 100644 index 000000000000..38133c55d765 --- /dev/null +++ b/ql/ql/src/queries/metrics/FLinesOfCode.ql @@ -0,0 +1,14 @@ +/** + * @name Lines of code in files + * @kind metric + * @description Measures the number of lines of code in each file, ignoring lines that + * contain only comments or whitespace. + * @metricType file + * @id ql/lines-of-code-in-files + */ + +import ql + +from File f, int n +where n = f.getNumberOfLinesOfCode() +select f, n order by n desc diff --git a/ql/ql/src/queries/metrics/FLinesOfComments.ql b/ql/ql/src/queries/metrics/FLinesOfComments.ql new file mode 100644 index 000000000000..b2ac7f924240 --- /dev/null +++ b/ql/ql/src/queries/metrics/FLinesOfComments.ql @@ -0,0 +1,13 @@ +/** + * @name Lines of comments in files + * @kind metric + * @description Measures the number of lines of comments in each file. + * @metricType file + * @id ql/lines-of-comments-in-files + */ + +import ql + +from File f, int n +where n = f.getNumberOfLinesOfComments() +select f, n order by n desc diff --git a/ql/ql/src/queries/performance/AbstractClassImport.ql b/ql/ql/src/queries/performance/AbstractClassImport.ql new file mode 100644 index 000000000000..b3dfceebed1a --- /dev/null +++ b/ql/ql/src/queries/performance/AbstractClassImport.ql @@ -0,0 +1,105 @@ +/** + * @name Bidirectional imports for abstract classes + * @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 + * @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() + ) +} + +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 = 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` */ +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 $@." + ) + ) and + // exclude results in experimental + not experimentalFile(sub.getLocation().getFile()) +} + +from Class ab, string msg, Class sub, Class sub2 +where alert(ab, msg, sub, sub2) +select ab, msg, sub, sub.getName(), sub2, sub2.getName() diff --git a/ql/ql/src/queries/performance/ClassPredicateDoesntMentionThis.ql b/ql/ql/src/queries/performance/ClassPredicateDoesntMentionThis.ql new file mode 100644 index 000000000000..9ed5870dcf3a --- /dev/null +++ b/ql/ql/src/queries/performance/ClassPredicateDoesntMentionThis.ql @@ -0,0 +1,86 @@ +/** + * @name Class predicate doesn't mention `this` + * @description A class predicate that doesn't use `this` (or a field) could instead be a classless predicate, and may cause a cartesian product. + * @kind problem + * @problem.severity warning + * @id ql/class-predicate-doesnt-use-this + * @tags performance + * @precision medium + */ + +import ql + +predicate usesThis(ClassPredicate pred) { + exists(ThisAccess th | th.getEnclosingPredicate() = pred) + or + exists(Super sup | sup.getEnclosingPredicate() = pred) + or + exists(FieldAccess f | f.getEnclosingPredicate() = pred) + or + // implicit this + exists(PredicateCall pc | pc.getEnclosingPredicate() = pred | + pc.getTarget() instanceof ClassPredicate + ) +} + +predicate isLiteralComparison(ComparisonFormula eq) { + exists(Expr lhs, Expr rhs | + eq.getOperator() = "=" and + eq.getAnOperand() = lhs and + eq.getAnOperand() = rhs and + ( + lhs instanceof ResultAccess + or + lhs instanceof ThisAccess + or + lhs instanceof VarAccess + ) and + ( + rhs instanceof Literal + or + exists(NewTypeBranch nt | + rhs.(Call).getTarget() = nt and + count(nt.getField(_)) = 0 + ) + ) + ) +} + +predicate conjParent(Formula par, Formula child) { child = par.(Conjunction).getAnOperand() } + +predicate isLiteralComparisons(Formula f) { + forex(ComparisonFormula child | conjParent*(f, child) | isLiteralComparison(child)) +} + +predicate isTrivialImplementation(Predicate pred) { + not exists(pred.getBody()) + or + exists(Formula bod | bod = pred.getBody() | + bod instanceof AnyCall + or + bod instanceof NoneCall + or + isLiteralComparisons(bod) + ) +} + +predicate isSingleton(Type ty) { + isTrivialImplementation(ty.(ClassType).getDeclaration().getCharPred()) + or + isSingleton(ty.getASuperType()) + or + exists(NewTypeBranch br | count(br.getField(_)) = 0 | + ty.(NewTypeBranchType).getDeclaration() = br + or + br = unique(NewTypeBranch br2 | br2 = ty.(NewTypeType).getDeclaration().getABranch()) + ) +} + +from ClassPredicate pred +where + not usesThis(pred) and + not isTrivialImplementation(pred) and + not isSingleton(pred.getDeclaringType()) and + not exists(ClassPredicate other | pred.overrides(other) or other.overrides(pred)) and + not pred.isOverride() +select pred, "This predicate could be a classless predicate, as it doesn't depend on `this`." diff --git a/ql/ql/src/queries/performance/DontUseGetAQlClass.ql b/ql/ql/src/queries/performance/DontUseGetAQlClass.ql new file mode 100644 index 000000000000..adbcb0bacccf --- /dev/null +++ b/ql/ql/src/queries/performance/DontUseGetAQlClass.ql @@ -0,0 +1,22 @@ +/** + * @name Don't use getAQlClass. + * @description Any use of getAQlClass causes both compile-time and runtime to be significantly slower. + * @kind problem + * @problem.severity warning + * @id ql/dont-use-getaqlclass + * @tags performance + * @precision very-high + */ + +import ql + +from Call call +where + ( + call.(PredicateCall).getPredicateName() = "getAQlClass" or + call.(MemberCall).getMemberName() = "getAQlClass" + ) and + not call.getLocation().getFile().getAbsolutePath().matches("%/" + ["meta", "test"] + "/%") and + not call.getLocation().getFile().getBaseName().toLowerCase() = + ["consistency.ql", "test.ql", "tst.ql", "tests.ql"] +select call, "Don't use .getAQlClass" diff --git a/ql/ql/src/queries/performance/InefficientStringComparison.ql b/ql/ql/src/queries/performance/InefficientStringComparison.ql new file mode 100644 index 000000000000..d268586423c6 --- /dev/null +++ b/ql/ql/src/queries/performance/InefficientStringComparison.ql @@ -0,0 +1,26 @@ +/** + * @name Inefficient string comparison + * @description The `.matches` predicate is usually the best performing way to compare strings. + * @kind problem + * @problem.severity error + * @id ql/inefficient-string-comparison + * @tags performance + * @precision high + */ + +import ql +import codeql_ql.performance.InefficientStringComparisonQuery + +from AstNode node, string msg +where + exists(EqFormula eq, FixPredicateCall call, String literal | + node = eq and msg = "Use " + getMessage(call, literal) + " instead." + | + eq.getAnOperand() = call and eq.getAnOperand() = literal + ) + or + exists(string matchesStr | + canUseMatchInsteadOfRegexpMatch(node, matchesStr) and + msg = "Use matches(\"" + matchesStr + "\") instead" + ) +select node, msg diff --git a/ql/ql/src/queries/performance/MissingNoinline.ql b/ql/ql/src/queries/performance/MissingNoinline.ql new file mode 100644 index 000000000000..bd20ee9e4590 --- /dev/null +++ b/ql/ql/src/queries/performance/MissingNoinline.ql @@ -0,0 +1,23 @@ +/** + * @name Missing `noinline` or `nomagic` annotation + * @description When a predicate is factored out to improve join-ordering, it should be marked as `noinline` or `nomagic`. + * @kind problem + * @problem.severity error + * @id ql/missing-noinline + * @tags performance + * @precision high + */ + +import ql + +from QLDoc doc, Predicate decl +where + doc.getContents().matches(["%join order%", "%join-order%"]) and + decl.getQLDoc() = doc and + not decl.getAnAnnotation() instanceof NoInline and + not decl.getAnAnnotation() instanceof NoMagic and + not decl.getAnAnnotation() instanceof NoOpt and + // If it's marked as inline it's probably because the QLDoc says something like + // "this predicate is inlined because it gives a better join-order". + not decl.getAnAnnotation() instanceof Inline +select decl, "This predicate might be inlined." diff --git a/ql/ql/src/queries/performance/MissingNomagic.ql b/ql/ql/src/queries/performance/MissingNomagic.ql new file mode 100644 index 000000000000..13084bf6ad62 --- /dev/null +++ b/ql/ql/src/queries/performance/MissingNomagic.ql @@ -0,0 +1,79 @@ +/** + * @name Candidate predicate not marked as `nomagic` + * @description A candidate predicate should be marked as `nomagic` to prevent unnecessary computation. + * @kind problem + * @problem.severity warning + * @id ql/cand-missing-nomagic + * @tags performance + * @precision medium + */ + +import ql + +/** + * Holds if the set of tuples satisfying `cand` is an upper bound for + * the set of tuples satisfying `f` + */ +predicate guards(Predicate cand, Formula f) { + forex(Formula child | child = f.(Disjunction).getAnOperand() | guards(cand, child)) + or + guards(cand, f.(Conjunction).getAnOperand()) + or + exists(Call call | f = call | + call.getTarget() = cand + or + guards(cand, call.getTarget().(Predicate).getBody()) + ) + or + exists(Quantifier q | f = q | guards(cand, [q.getFormula(), q.getRange()])) + or + exists(IfFormula ifFormula | ifFormula = f | + guards(cand, ifFormula.getThenPart()) and guards(cand, ifFormula.getElsePart()) + ) +} + +pragma[noinline] +predicate hasNameWithNumberSuffix(Predicate p, string name, int n) { + n = [1 .. 10] and + p.getName() = name + n.toString() +} + +/** + * A candidate predicate for another predicate. + * + * A predicate `p0` is a candidate for another predicate `p` when the tuples + * that satisfy `p0` upper bounds the set of tuples that satisfy `p`. + */ +class CandidatePredicate extends Predicate { + Predicate pred; + + CandidatePredicate() { + // This predicate "guards" the predicate `pred` (i.e., it's always evaluated before `pred`). + guards(this, pred.getBody()) and + ( + // The name of `pred` is "foo", and the name of this predicate is `foo0`, or `fooHelper`, or any + // other the other cases. + pragma[only_bind_into](pred).getName() = + this.getName() + .regexpCapture("(.+)" + ["0", "helper", "aux", "cand", "Helper", "Aux", "Cand"], 1) + or + // Or this this predicate is named "foo02" and `pred` is named "foo01". + exists(int n, string name | + hasNameWithNumberSuffix(pred, name, n) and + hasNameWithNumberSuffix(this, name, n - 1) + ) + ) + } + + /** Holds if this predicate is a candidate predicate for `p`. */ + predicate isCandidateFor(Predicate p) { p = pred } +} + +from CandidatePredicate cand, Predicate pred +where + // The candidate predicate is not in a test directory. + not cand.getLocation().getFile().getAbsolutePath().matches("%/" + ["meta", "test"] + "/%") and + cand.isCandidateFor(pred) and + not cand.getAnAnnotation() instanceof NoMagic and + not cand.getAnAnnotation() instanceof NoOpt +select cand, "Candidate predicate to $@ is not marked as nomagic.", pred, pred.getName() diff --git a/ql/ql/src/queries/performance/NonInitialStdLibImport.ql b/ql/ql/src/queries/performance/NonInitialStdLibImport.ql new file mode 100644 index 000000000000..88296d895fc9 --- /dev/null +++ b/ql/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" diff --git a/ql/ql/src/queries/performance/TransitiveStep.ql b/ql/ql/src/queries/performance/TransitiveStep.ql new file mode 100644 index 000000000000..d92dd44b76c0 --- /dev/null +++ b/ql/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.getOperator() = "=" 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.getOperator() = "=" 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" diff --git a/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql b/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql new file mode 100644 index 000000000000..4311038df504 --- /dev/null +++ b/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql @@ -0,0 +1,212 @@ +/** + * @name Var only used in one side of disjunct. + * @description Only using a variable on one side of a disjunction can cause a cartesian product. + * @kind problem + * @problem.severity warning + * @id ql/var-unused-in-disjunct + * @tags maintainability + * performance + * @precision high + */ + +import ql + +/** + * Holds if `node` bind `var` in a (transitive) child node. + * Is a practical approximation that ignores `not` and many other features. + */ +pragma[noinline] +predicate alwaysBindsVar(VarDef var, AstNode node) { + // base case + node.(VarAccess).getDeclaration() = var and + not isSmallType(var.getType()) // <- early pruning + or + // recursive cases + alwaysBindsVar(var, node.getAChild(_)) and // the recursive step, go one step up to the parent. + not node.(FullAggregate).getAnArgument() = var and // except if the parent defines the variable, then we stop. + not node.(Quantifier).getAnArgument() = var and + not node instanceof EffectiveDisjunction // for disjunctions, we need to check both sides. + or + exists(EffectiveDisjunction disj | disj = node | + alwaysBindsVar(var, disj.getLeft()) and + alwaysBindsVar(var, disj.getRight()) + ) + or + exists(EffectiveDisjunction disj | disj = node | + alwaysBindsVar(var, disj.getAnOperand()) and + disj.getAnOperand() instanceof NoneCall + ) + or + exists(IfFormula ifForm | ifForm = node | alwaysBindsVar(var, ifForm.getCondition())) +} + +/** + * Holds if we assume `t` is a small type, and + * variables of this type are therefore not an issue in cartesian products. + */ +predicate isSmallType(Type t) { + t.getName() = "string" // DataFlow::Configuration and the like + or + exists(NewType newType | newType = t.getDeclaration() | + forex(NewTypeBranch branch | branch = newType.getABranch() | branch.getArity() = 0) + ) + or + t.getName() = "boolean" + or + exists(NewType newType | newType = t.getDeclaration() | + forex(NewTypeBranch branch | branch = newType.getABranch() | + isSmallType(branch.getReturnType()) + ) + ) + or + exists(NewTypeBranch branch | t = branch.getReturnType() | + forall(Type param | param = branch.getParameterType(_) | isSmallType(param)) + ) + or + isSmallType(t.getASuperType()) +} + +/** + * Holds if `pred` is inlined. + */ +predicate isInlined(Predicate pred) { + exists(Annotation inline | + inline = pred.getAnAnnotation() and + inline.getName() = "pragma" and + inline.getArgs(0).getValue() = "inline" + ) + or + pred.getAnAnnotation().getName() = "bindingset" +} + +/** + * An AstNode that acts like a disjunction. + */ +class EffectiveDisjunction extends AstNode { + EffectiveDisjunction() { + this instanceof IfFormula + or + this instanceof Disjunction + } + + /** Gets the left operand of this disjunction. */ + AstNode getLeft() { + result = this.(IfFormula).getThenPart() + or + result = this.(Disjunction).getLeft() + } + + /** Gets the right operand of this disjunction. */ + AstNode getRight() { + result = this.(IfFormula).getElsePart() + or + result = this.(Disjunction).getRight() + } + + /** Gets any of the operands of this disjunction. */ + AstNode getAnOperand() { result = [this.getLeft(), this.getRight()] } +} + +/** + * Holds if `disj` only uses `var` in one of its branches. + */ +pragma[noinline] +predicate onlyUseInOneBranch(EffectiveDisjunction disj, VarDef var) { + alwaysBindsVar(var, disj.getLeft()) and + not alwaysBindsVar(var, disj.getRight()) + or + not alwaysBindsVar(var, disj.getLeft()) and + alwaysBindsVar(var, disj.getRight()) +} + +/** + * Holds if `comp` is an equality comparison that has a low number of results. + */ +predicate isTinyAssignment(ComparisonFormula comp) { + comp.getOperator() = "=" and + ( + isSmallType(comp.getAnOperand().getType()) + or + comp.getAnOperand() instanceof Literal + ) +} + +/** + * An AstNode that acts like a conjunction. + */ +class EffectiveConjunction extends AstNode { + EffectiveConjunction() { + this instanceof Conjunction + or + this instanceof Exists + } + + /** Gets the left operand of this conjunction */ + Formula getLeft() { + result = this.(Conjunction).getLeft() + or + result = this.(Exists).getRange() + } + + /** Gets the right operand of this conjunction */ + Formula getRight() { + result = this.(Conjunction).getRight() + or + result = this.(Exists).getFormula() + } +} + +/** + * Holds if `node` is a sub-node of a node that always binds `var`. + */ +predicate varIsAlwaysBound(VarDef var, AstNode node) { + // base case + alwaysBindsVar(var, node) and + onlyUseInOneBranch(_, var) // <- manual magic + or + // recursive cases + exists(AstNode parent | node.getParent() = parent | varIsAlwaysBound(var, parent)) + or + exists(EffectiveConjunction parent | + varIsAlwaysBound(var, parent.getLeft()) and + node = parent.getRight() + or + varIsAlwaysBound(var, parent.getRight()) and + node = parent.getLeft() + ) + or + exists(IfFormula ifForm | varIsAlwaysBound(var, ifForm.getCondition()) | + node = [ifForm.getThenPart(), ifForm.getElsePart()] + ) + or + exists(Forall for | varIsAlwaysBound(var, for.getRange()) | node = for.getFormula()) +} + +/** + * Holds if `disj` only uses `var` in one of its branches. + * And we should report it as being a bad thing. + */ +predicate badDisjunction(EffectiveDisjunction disj, VarDef var) { + onlyUseInOneBranch(disj, var) and + // it's fine if it's always bound further up + not varIsAlwaysBound(var, disj) and + // none() on one side makes everything fine. (this happens, it's a type-system hack) + not disj.getAnOperand() instanceof NoneCall and + // inlined predicates might bind unused variables in the context they are used in. + not ( + isInlined(disj.getEnclosingPredicate()) and + var = disj.getEnclosingPredicate().getParameter(_) + ) and + // recursion prevention never runs, it's a compile-time check, so we remove those results here + not disj.getEnclosingPredicate().getParent().(Class).getName().matches("%RecursionPrevention") and // these are by design + // not a small type + not isSmallType(var.getType()) and + // one of the branches is a tiny assignment. These are usually intentional cartesian products (and not too big). + not isTinyAssignment(disj.getAnOperand()) +} + +from EffectiveDisjunction disj, VarDef var +where + badDisjunction(disj, var) and + not badDisjunction(disj.getParent(), var) // avoid duplicate reporting of the same error +select disj, "The variable " + var.getName() + " is only used in one side of disjunct." diff --git a/ql/ql/src/queries/style/GetAPrimaryQlClassConsistency.ql b/ql/ql/src/queries/style/GetAPrimaryQlClassConsistency.ql new file mode 100644 index 000000000000..dd5da5d78eab --- /dev/null +++ b/ql/ql/src/queries/style/GetAPrimaryQlClassConsistency.ql @@ -0,0 +1,27 @@ +/** + * @name Inconsistent getAPrimaryQlClass predicate + * @description A getAPrimaryQlClass predicate should result in the name of the class. + * @kind problem + * @problem.severity error + * @id ql/primary-ql-class-consistency + * @tags correctness + * @precision very-high + */ + +import ql + +from ClassPredicate pred, String constant +where + exists(string className, string constantName | + pred.getParent().getName() = className and + pred.getName() = "getAPrimaryQlClass" and + constant = pred.getBody().(ComparisonFormula).getRightOperand() and + constant.(String).getValue() = constantName and + // might be "Foo::classname", detect by matching with a regexp + not constantName.regexpMatch(".*\\b" + className + "$") and + // ignore constants with "?" in them + not constantName.regexpMatch(".*\\?.*") + ) +select pred, + "The getAPrimaryQlClass predicate $@ instead of the class name \"" + pred.getParent().getName() + + "\".", constant, "results in \"" + constant.getValue() + "\"" diff --git a/ql/ql/src/queries/style/IfWithElseNone.ql b/ql/ql/src/queries/style/IfWithElseNone.ql new file mode 100644 index 000000000000..c563c28e9f89 --- /dev/null +++ b/ql/ql/src/queries/style/IfWithElseNone.ql @@ -0,0 +1,15 @@ +/** + * @name Use of 'if' with a 'none()' branch. + * @description Using 'if p() then q() else none()' is bad style. It should be rewritten as 'p() and q()'. + * @kind problem + * @problem.severity warning + * @id ql/if-with-none + * @precision very-high + * @tags maintainability + */ + +import ql + +from IfFormula ifFormula +where ifFormula.getElsePart() instanceof NoneCall or ifFormula.getThenPart() instanceof NoneCall +select ifFormula, "Use a conjunction instead." diff --git a/ql/ql/src/queries/style/ImplicitThis.ql b/ql/ql/src/queries/style/ImplicitThis.ql new file mode 100644 index 000000000000..cd456bbf0df8 --- /dev/null +++ b/ql/ql/src/queries/style/ImplicitThis.ql @@ -0,0 +1,16 @@ +/** + * @name Using implicit `this` + * @description Writing member predicate calls with an implicit `this` can be confusing + * @kind problem + * @problem.severity warning + * @precision high + * @id ql/implicit-this + * @tags maintainability + */ + +import ql +import codeql_ql.style.ImplicitThisQuery + +from PredicateCall c +where c = confusingImplicitThisCall(_) +select c, "Use of implicit `this`." diff --git a/ql/ql/src/queries/style/LibraryAnnotation.ql b/ql/ql/src/queries/style/LibraryAnnotation.ql new file mode 100644 index 000000000000..cf4a4bc8232c --- /dev/null +++ b/ql/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." diff --git a/ql/ql/src/queries/style/MissingOverride.ql b/ql/ql/src/queries/style/MissingOverride.ql new file mode 100644 index 000000000000..a06222e29a8b --- /dev/null +++ b/ql/ql/src/queries/style/MissingOverride.ql @@ -0,0 +1,20 @@ +/** + * @name Missing override annotation + * @description Predicates that overide another predicate should have an `override` annotation. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id ql/missing-override + * @tags maintainability + */ + +import ql + +string getQualifiedName(ClassPredicate p) { + result = p.getDeclaringType().getName() + "." + p.getName() +} + +from ClassPredicate pred, ClassPredicate sup +where pred.overrides(sup) and not pred.isOverride() +select pred, getQualifiedName(pred) + " overrides $@ but does not have an override annotation.", + sup, getQualifiedName(sup) diff --git a/ql/ql/src/queries/style/OverridingParameterName.ql b/ql/ql/src/queries/style/OverridingParameterName.ql new file mode 100644 index 000000000000..6973c046ae0e --- /dev/null +++ b/ql/ql/src/queries/style/OverridingParameterName.ql @@ -0,0 +1,31 @@ +/** + * @name Using a different paramater name than used in the super-predicate. + * @description Using another parameter can be an indication of copy-pasted code, or a mistake. + * @kind problem + * @problem.severity warning + * @id ql/override-parameter-name + * @tags correctness + * maintainability + * @precision medium + */ + +import ql + +pragma[noinline] +private predicate getAnOverridingParameter( + ClassPredicate pred, ClassPredicate sup, VarDecl parameter, int index +) { + pred.overrides(sup) and + parameter = pred.getParameter(index) +} + +from ClassPredicate pred, ClassPredicate sup, VarDecl parameter, int index +where + getAnOverridingParameter(pred, sup, parameter, index) and + sup.getParameter(index).getName() != pred.getParameter(index).getName() and + // avoid duplicated alerts with `ql/override-swapped-name` + not exists(int other | other != index | + sup.getParameter(other).getName() = pred.getParameter(index).getName() + ) +select parameter, pred.getParameter(index).getName() + " was $@ in the super class.", + sup.getParameter(index), "named " + sup.getParameter(index).getName() diff --git a/ql/ql/src/queries/style/RankOne.ql b/ql/ql/src/queries/style/RankOne.ql new file mode 100644 index 000000000000..edfb242f174e --- /dev/null +++ b/ql/ql/src/queries/style/RankOne.ql @@ -0,0 +1,16 @@ +/** + * @name Selecting minimum element using `rank[1]` + * @description Selecting the minimum element using `rank[1](..)` performs worse than doing the same thing with `min(..)`. + * @kind problem + * @problem.severity warning + * @id ql/rank-one + * @tags performance, + * maintainability + * @precision very-high + */ + +import ql + +from Rank r +where r.getRankExpr().(Integer).getValue() = 1 +select r, "Using rank[1](..) is an anti-pattern, use min(..) instead." diff --git a/ql/ql/src/queries/style/RedundantInlineCast.ql b/ql/ql/src/queries/style/RedundantInlineCast.ql new file mode 100644 index 000000000000..29dc83e52de2 --- /dev/null +++ b/ql/ql/src/queries/style/RedundantInlineCast.ql @@ -0,0 +1,16 @@ +/** + * @name Redundant inline cast + * @description Redundant inline casts + * @kind problem + * @problem.severity error + * @id ql/redundant-inline-cast + * @tags maintainability + * @precision high + */ + +import ql +import codeql_ql.style.RedundantInlineCastQuery + +from RedundantInlineCast cast +select cast, "Redundant cast to $@", cast.getTypeExpr(), + cast.getTypeExpr().getResolvedType().getName() diff --git a/ql/ql/src/queries/style/RegexpInsteadOfPattern.ql b/ql/ql/src/queries/style/RegexpInsteadOfPattern.ql new file mode 100644 index 000000000000..69541ed30eb0 --- /dev/null +++ b/ql/ql/src/queries/style/RegexpInsteadOfPattern.ql @@ -0,0 +1,34 @@ +/** + * @name RegexpInsteadOfPattern + * @description The `matches` builtin predicate takes a special pattern format as an input, not a regular expression. + * @kind problem + * @problem.severity warning + * @id ql/rexexp-pattern + * @precision medium + */ + +import ql + +/** + * Gets a regular expression pattern that matches the syntax of likely regular expressions. + */ +private string getALikelyRegExpPattern() { + result = "/.*/[gimuy]{1,5}" or // pattern with at least one flag: /foo/i + result = "/\\^.*/[gimuy]{0,5}" or // pattern with anchor: /^foo/ + result = "/.*\\$/[gimuy]{0,5}" or // pattern with anchor: /foo$/ + result = "\\^.*\\$" or // pattern body with anchors: ^foo$ + result = ".*(?= 4 +select d, msg diff --git a/ql/ql/src/queries/style/docs/ClassDocs.ql b/ql/ql/src/queries/style/docs/ClassDocs.ql new file mode 100644 index 000000000000..1dad08679962 --- /dev/null +++ b/ql/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 %", "INTERNAL%", "DEPRECATED%"]) +} + +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'." diff --git a/ql/ql/src/queries/style/docs/MissingQLDoc.ql b/ql/ql/src/queries/style/docs/MissingQLDoc.ql new file mode 100644 index 000000000000..c7da02073a38 --- /dev/null +++ b/ql/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." diff --git a/ql/ql/src/queries/style/docs/NonUSSpelling.ql b/ql/ql/src/queries/style/docs/NonUSSpelling.ql new file mode 100644 index 000000000000..8861629ca87b --- /dev/null +++ b/ql/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 + "'." diff --git a/ql/ql/src/queries/summary/LinesOfCode.ql b/ql/ql/src/queries/summary/LinesOfCode.ql new file mode 100644 index 000000000000..cb5ef6174769 --- /dev/null +++ b/ql/ql/src/queries/summary/LinesOfCode.ql @@ -0,0 +1,15 @@ +/** + * @id ql/summary/lines-of-code + * @name Total lines of QL code in the database + * @description The total number of lines of QL code from the source code + * directory, including external libraries and auto-generated files. This is a + * useful metric of the size of a database. This query counts the lines of + * code, excluding whitespace or comments. + * @kind metric + * @tags summary + * lines-of-code + */ + +import ql + +select sum(File f | exists(f.getRelativePath()) | f.getNumberOfLinesOfCode()) diff --git a/ql/ql/src/queries/summary/LinesOfUserCode.ql b/ql/ql/src/queries/summary/LinesOfUserCode.ql new file mode 100644 index 000000000000..fc996c1cec28 --- /dev/null +++ b/ql/ql/src/queries/summary/LinesOfUserCode.ql @@ -0,0 +1,18 @@ +/** + * @id ql/summary/lines-of-user-code + * @name Total Lines of user written QL code in the database + * @description The total number of lines of QL code from the source code + * directory, excluding external library and auto-generated files. This + * query counts the lines of code, excluding whitespace or comments. + * @kind metric + * @tags summary + */ + +import ql + +select sum(File f | + f.fromSource() and + exists(f.getRelativePath()) + | + f.getNumberOfLinesOfCode() + ) diff --git a/ql/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql b/ql/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql new file mode 100644 index 000000000000..3f68ce8fdc9f --- /dev/null +++ b/ql/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql @@ -0,0 +1,15 @@ +/** + * @id ql/summary/number-of-files-extracted-with-errors + * @name Total number of files that were extracted with errors + * @description The total number of QL code files that we extracted, but where + * at least one extraction error occurred in the process. + * @kind metric + * @tags summary + */ + +import ql +import codeql_ql.Diagnostics + +select count(File f | + exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath()) + ) diff --git a/ql/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql b/ql/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql new file mode 100644 index 000000000000..d7fa1bb16021 --- /dev/null +++ b/ql/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql @@ -0,0 +1,15 @@ +/** + * @id ql/summary/number-of-successfully-extracted-files + * @name Total number of files that were extracted without error + * @description The total number of QL code files that we extracted without + * encountering any extraction errors + * @kind metric + * @tags summary + */ + +import ql +import codeql_ql.Diagnostics + +select count(File f | + not exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath()) + ) diff --git a/ql/ql/test/callgraph/Bar.qll b/ql/ql/test/callgraph/Bar.qll new file mode 100644 index 000000000000..07add85d6372 --- /dev/null +++ b/ql/ql/test/callgraph/Bar.qll @@ -0,0 +1,9 @@ +import ql + +module Firebase { + module Database { + query predicate ref(int i) { i = snapshot() } + } + + int snapshot() { result = 2 } +} diff --git a/ql/ql/test/callgraph/Baz.qll b/ql/ql/test/callgraph/Baz.qll new file mode 100644 index 000000000000..371439d85303 --- /dev/null +++ b/ql/ql/test/callgraph/Baz.qll @@ -0,0 +1,11 @@ +class Foo extends string { + Foo() { this = "Foo" } + + string getImportedPath() { none() } +} + +class Bar extends string, Foo { + Bar() { exists(Foo.super.getImportedPath()) } + + override string getImportedPath() { none() } +} diff --git a/ql/ql/test/callgraph/Foo.qll b/ql/ql/test/callgraph/Foo.qll new file mode 100644 index 000000000000..1a2ac90e06e4 --- /dev/null +++ b/ql/ql/test/callgraph/Foo.qll @@ -0,0 +1,47 @@ +import ql + +predicate foo() { none() } + +query predicate test() { foo() } + +class Foo extends AstNode { + predicate bar() { none() } + + predicate baz() { bar() } +} + +class Sub extends Foo { + override predicate baz() { super.baz() } +} + +query predicate test2() { any(Foo f).bar() } + +module Aliases { + predicate myThing2(int i, int j) { i = 2 and j = 3 } + + predicate myThing0() { any() } + + predicate alias0 = myThing0/0; + + predicate alias2 = myThing2/2; + + query predicate test3() { + alias2(3, 4) // doesn't work. + or + alias0() // <- works + } +} + +module Buildins { + predicate replaceAll(string s) { "foo".replaceAll("foo", "bar") = s } + + predicate regexpCapture(string s) { "foo".regexpCapture("\\w", 1) = s } +} + +cached +newtype TApiNode = MkRoot() + +private predicate edge(TApiNode a, TApiNode b) { a = b } + +cached +int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) diff --git a/ql/ql/test/callgraph/Overrides.qll b/ql/ql/test/callgraph/Overrides.qll new file mode 100644 index 000000000000..1adcab1b949f --- /dev/null +++ b/ql/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/ql/test/callgraph/callgraph.expected b/ql/ql/test/callgraph/callgraph.expected new file mode 100644 index 000000000000..33fb31cef9da --- /dev/null +++ b/ql/ql/test/callgraph/callgraph.expected @@ -0,0 +1,29 @@ +getTarget +| Bar.qll:5:38:5:47 | PredicateCall | Bar.qll:8:3:8:31 | ClasslessPredicate snapshot | +| Baz.qll:8:18:8:44 | MemberCall | Baz.qll:4:3:4:37 | ClassPredicate getImportedPath | +| 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 | +| Foo.qll:17:27:17:42 | MemberCall | Foo.qll:8:3:8:28 | ClassPredicate bar | +| Foo.qll:29:5:29:16 | PredicateCall | Foo.qll:20:3:20:54 | ClasslessPredicate myThing2 | +| 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 | +| 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 | +| 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 | +exprPredicate +| Foo.qll:24:22:24:31 | predicate | Foo.qll:22:3:22:32 | ClasslessPredicate myThing0 | +| Foo.qll:26:22:26:31 | predicate | Foo.qll:20:3:20:54 | ClasslessPredicate myThing2 | +| Foo.qll:47:55:47:62 | predicate | Foo.qll:42:20:42:27 | NewTypeBranch MkRoot | +| Foo.qll:47:65:47:70 | predicate | Foo.qll:44:9:44:56 | ClasslessPredicate edge | diff --git a/ql/ql/test/callgraph/callgraph.ql b/ql/ql/test/callgraph/callgraph.ql new file mode 100644 index 000000000000..bf7f2ed12933 --- /dev/null +++ b/ql/ql/test/callgraph/callgraph.ql @@ -0,0 +1,7 @@ +import ql + +query AstNode getTarget(Call call) { result = call.getTarget() } + +query YAML::QLPack dependsOn(YAML::QLPack pack) { result = pack.getADependency() } + +query Predicate exprPredicate(PredicateExpr expr) { result = expr.getResolvedPredicate() } diff --git a/ql/ql/test/callgraph/packs/lib/LibThing/Foo.qll b/ql/ql/test/callgraph/packs/lib/LibThing/Foo.qll new file mode 100644 index 000000000000..56454a955574 --- /dev/null +++ b/ql/ql/test/callgraph/packs/lib/LibThing/Foo.qll @@ -0,0 +1 @@ +predicate foo(int i) { i = 3 } diff --git a/ql/ql/test/callgraph/packs/lib/qlpack.yml b/ql/ql/test/callgraph/packs/lib/qlpack.yml new file mode 100644 index 000000000000..92e83d1e3d8c --- /dev/null +++ b/ql/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/ql/test/callgraph/packs/src/SrcThing.qll b/ql/ql/test/callgraph/packs/src/SrcThing.qll new file mode 100644 index 000000000000..77c812235d91 --- /dev/null +++ b/ql/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/ql/test/callgraph/packs/src/qlpack.yml b/ql/ql/test/callgraph/packs/src/qlpack.yml new file mode 100644 index 000000000000..dcf148fe721a --- /dev/null +++ b/ql/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/ql/ql/test/printAst/Foo.qll b/ql/ql/test/printAst/Foo.qll new file mode 100644 index 000000000000..17e4a4d636dd --- /dev/null +++ b/ql/ql/test/printAst/Foo.qll @@ -0,0 +1,27 @@ +import javascript + +class Foo extends @bar { + Foo() { 1 = 2 } + + string toString() { result = "Foo" } +} + +query predicate foo(Foo f) { + f = rank[2](Foo inner | inner.toString() = "foo" | inner order by inner.toString()) +} + +predicate calls(Foo f) { + calls(f) + or + "foo" = f.toString(1, 2, 3) + or + f.(Foo).toString() = "bar" + or + f.(Foo) = f + or + f = any(Foo f) + or + 2 = 1 + (2 + (3 + 4)) + or + true = false +} diff --git a/ql/ql/test/printAst/printAst.expected b/ql/ql/test/printAst/printAst.expected new file mode 100644 index 000000000000..45e8497dfad7 --- /dev/null +++ b/ql/ql/test/printAst/printAst.expected @@ -0,0 +1,395 @@ +nodes +| Foo.qll:1:1:1:17 | Import | semmle.label | [Import] Import | +| Foo.qll:1:1:1:17 | Import | semmle.order | 1 | +| Foo.qll:1:1:27:2 | TopLevel | semmle.label | [TopLevel] TopLevel | +| Foo.qll:1:1:27:2 | TopLevel | semmle.order | 1 | +| Foo.qll:3:1:7:1 | Class Foo | semmle.label | [Class] Class Foo | +| Foo.qll:3:1:7:1 | Class Foo | semmle.order | 3 | +| Foo.qll:3:19:3:22 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:3:19:3:22 | TypeExpr | semmle.order | 4 | +| Foo.qll:4:3:4:17 | CharPred Foo | semmle.label | [CharPred] CharPred Foo | +| Foo.qll:4:3:4:17 | CharPred Foo | semmle.order | 5 | +| Foo.qll:4:11:4:11 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:4:11:4:11 | Integer | semmle.order | 6 | +| Foo.qll:4:11:4:15 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:4:11:4:15 | ComparisonFormula | semmle.order | 6 | +| Foo.qll:4:15:4:15 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:4:15:4:15 | Integer | semmle.order | 8 | +| Foo.qll:6:3:6:8 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:6:3:6:8 | TypeExpr | semmle.order | 9 | +| Foo.qll:6:3:6:38 | ClassPredicate toString | semmle.label | [ClassPredicate] ClassPredicate toString | +| Foo.qll:6:3:6:38 | ClassPredicate toString | semmle.order | 9 | +| Foo.qll:6:23:6:28 | result | semmle.label | [ResultAccess] result | +| Foo.qll:6:23:6:28 | result | semmle.order | 11 | +| Foo.qll:6:23:6:36 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:6:23:6:36 | ComparisonFormula | semmle.order | 11 | +| Foo.qll:6:32:6:36 | String | semmle.label | [String] String | +| Foo.qll:6:32:6:36 | String | semmle.order | 13 | +| Foo.qll:9:1:9:5 | annotation | semmle.label | [Annotation] annotation | +| Foo.qll:9:1:9:5 | annotation | semmle.order | 14 | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.label | [ClasslessPredicate] ClasslessPredicate foo | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 15 | +| Foo.qll:9:21:9:23 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 16 | +| Foo.qll:9:21:9:25 | f | semmle.label | [VarDecl] f | +| Foo.qll:9:21:9:25 | f | semmle.order | 16 | +| Foo.qll:10:3:10:3 | f | semmle.label | [VarAccess] f | +| Foo.qll:10:3:10:3 | f | semmle.order | 18 | +| Foo.qll:10:3:10:85 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 18 | +| Foo.qll:10:7:10:85 | Rank | semmle.label | [Rank] Rank | +| Foo.qll:10:7:10:85 | Rank | semmle.order | 20 | +| Foo.qll:10:12:10:12 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:10:12:10:12 | Integer | semmle.order | 21 | +| Foo.qll:10:15:10:17 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 22 | +| Foo.qll:10:15:10:23 | inner | semmle.label | [VarDecl] inner | +| Foo.qll:10:15:10:23 | inner | semmle.order | 22 | +| Foo.qll:10:27:10:31 | inner | semmle.label | [VarAccess] inner | +| Foo.qll:10:27:10:31 | inner | semmle.order | 24 | +| Foo.qll:10:27:10:42 | MemberCall | semmle.label | [MemberCall] MemberCall | +| Foo.qll:10:27:10:42 | MemberCall | semmle.order | 24 | +| Foo.qll:10:27:10:50 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 24 | +| Foo.qll:10:46:10:50 | String | semmle.label | [String] String | +| Foo.qll:10:46:10:50 | String | semmle.order | 27 | +| Foo.qll:10:54:10:58 | inner | semmle.label | [VarAccess] inner | +| Foo.qll:10:54:10:58 | inner | semmle.order | 28 | +| Foo.qll:10:69:10:73 | inner | semmle.label | [VarAccess] inner | +| Foo.qll:10:69:10:73 | inner | semmle.order | 29 | +| Foo.qll:10:69:10:84 | MemberCall | semmle.label | [MemberCall] MemberCall | +| Foo.qll:10:69:10:84 | MemberCall | semmle.order | 29 | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.label | [ClasslessPredicate] ClasslessPredicate calls | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 31 | +| Foo.qll:13:17:13:19 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 32 | +| Foo.qll:13:17:13:21 | f | semmle.label | [VarDecl] f | +| Foo.qll:13:17:13:21 | f | semmle.order | 32 | +| Foo.qll:14:3:14:10 | PredicateCall | semmle.label | [PredicateCall] PredicateCall | +| Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 34 | +| Foo.qll:14:3:16:29 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:16:29 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:18:28 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:18:28 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:20:13 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:20:13 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:22:16 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:22:16 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:24:23 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:24:23 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:26:14 | Disjunction | semmle.label | [Disjunction] Disjunction | +| Foo.qll:14:3:26:14 | Disjunction | semmle.order | 34 | +| Foo.qll:14:9:14:9 | f | semmle.label | [VarAccess] f | +| Foo.qll:14:9:14:9 | f | semmle.order | 41 | +| Foo.qll:16:3:16:7 | String | semmle.label | [String] String | +| Foo.qll:16:3:16:7 | String | semmle.order | 42 | +| Foo.qll:16:3:16:29 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 42 | +| Foo.qll:16:11:16:11 | f | semmle.label | [VarAccess] f | +| Foo.qll:16:11:16:11 | f | semmle.order | 44 | +| Foo.qll:16:11:16:29 | MemberCall | semmle.label | [MemberCall] MemberCall | +| Foo.qll:16:11:16:29 | MemberCall | semmle.order | 44 | +| Foo.qll:16:22:16:22 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:16:22:16:22 | Integer | semmle.order | 46 | +| Foo.qll:16:25:16:25 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:16:25:16:25 | Integer | semmle.order | 47 | +| Foo.qll:16:28:16:28 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:16:28:16:28 | Integer | semmle.order | 48 | +| Foo.qll:18:3:18:3 | f | semmle.label | [VarAccess] f | +| Foo.qll:18:3:18:3 | f | semmle.order | 49 | +| Foo.qll:18:3:18:9 | InlineCast | semmle.label | [InlineCast] InlineCast | +| Foo.qll:18:3:18:9 | InlineCast | semmle.order | 49 | +| Foo.qll:18:3:18:20 | MemberCall | semmle.label | [MemberCall] MemberCall | +| Foo.qll:18:3:18:20 | MemberCall | semmle.order | 49 | +| Foo.qll:18:3:18:28 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 49 | +| Foo.qll:18:6:18:8 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 53 | +| Foo.qll:18:24:18:28 | String | semmle.label | [String] String | +| Foo.qll:18:24:18:28 | String | semmle.order | 54 | +| Foo.qll:20:3:20:3 | f | semmle.label | [VarAccess] f | +| Foo.qll:20:3:20:3 | f | semmle.order | 55 | +| Foo.qll:20:3:20:9 | InlineCast | semmle.label | [InlineCast] InlineCast | +| Foo.qll:20:3:20:9 | InlineCast | semmle.order | 55 | +| Foo.qll:20:3:20:13 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 55 | +| Foo.qll:20:6:20:8 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 58 | +| Foo.qll:20:13:20:13 | f | semmle.label | [VarAccess] f | +| Foo.qll:20:13:20:13 | f | semmle.order | 59 | +| Foo.qll:22:3:22:3 | f | semmle.label | [VarAccess] f | +| Foo.qll:22:3:22:3 | f | semmle.order | 60 | +| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 60 | +| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | [FullAggregate[any]] FullAggregate[any] | +| Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 62 | +| Foo.qll:22:11:22:13 | TypeExpr | semmle.label | [TypeExpr] TypeExpr | +| Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 63 | +| Foo.qll:22:11:22:15 | f | semmle.label | [VarDecl] f | +| Foo.qll:22:11:22:15 | f | semmle.order | 63 | +| Foo.qll:24:3:24:3 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:24:3:24:3 | Integer | semmle.order | 65 | +| Foo.qll:24:3:24:23 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 65 | +| Foo.qll:24:7:24:7 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:24:7:24:7 | Integer | semmle.order | 67 | +| Foo.qll:24:7:24:23 | AddExpr | semmle.label | [AddExpr] AddExpr | +| Foo.qll:24:7:24:23 | AddExpr | semmle.order | 67 | +| Foo.qll:24:12:24:12 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:24:12:24:12 | Integer | semmle.order | 69 | +| Foo.qll:24:12:24:22 | AddExpr | semmle.label | [AddExpr] AddExpr | +| Foo.qll:24:12:24:22 | AddExpr | semmle.order | 69 | +| Foo.qll:24:17:24:17 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:24:17:24:17 | Integer | semmle.order | 71 | +| Foo.qll:24:17:24:21 | AddExpr | semmle.label | [AddExpr] AddExpr | +| Foo.qll:24:17:24:21 | AddExpr | semmle.order | 71 | +| Foo.qll:24:21:24:21 | Integer | semmle.label | [Integer] Integer | +| Foo.qll:24:21:24:21 | Integer | semmle.order | 73 | +| Foo.qll:26:3:26:6 | Boolean | semmle.label | [Boolean] Boolean | +| Foo.qll:26:3:26:6 | Boolean | semmle.order | 74 | +| Foo.qll:26:3:26:14 | ComparisonFormula | semmle.label | [ComparisonFormula] ComparisonFormula | +| Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 74 | +| Foo.qll:26:10:26:14 | Boolean | semmle.label | [Boolean] Boolean | +| Foo.qll:26:10:26:14 | Boolean | semmle.order | 76 | +| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs | +| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs | +| file://:0:0:0:0 | acos | semmle.label | [BuiltinPredicate] acos | +| file://:0:0:0:0 | any | semmle.label | [BuiltinPredicate] any | +| file://:0:0:0:0 | atan | semmle.label | [BuiltinPredicate] atan | +| file://:0:0:0:0 | bitAnd | semmle.label | [BuiltinPredicate] bitAnd | +| file://:0:0:0:0 | bitNot | semmle.label | [BuiltinPredicate] bitNot | +| file://:0:0:0:0 | bitOr | semmle.label | [BuiltinPredicate] bitOr | +| file://:0:0:0:0 | bitShiftLeft | semmle.label | [BuiltinPredicate] bitShiftLeft | +| file://:0:0:0:0 | bitShiftRight | semmle.label | [BuiltinPredicate] bitShiftRight | +| file://:0:0:0:0 | bitShiftRightSigned | semmle.label | [BuiltinPredicate] bitShiftRightSigned | +| file://:0:0:0:0 | bitXor | semmle.label | [BuiltinPredicate] bitXor | +| file://:0:0:0:0 | booleanAnd | semmle.label | [BuiltinPredicate] booleanAnd | +| file://:0:0:0:0 | booleanNot | semmle.label | [BuiltinPredicate] booleanNot | +| file://:0:0:0:0 | booleanOr | semmle.label | [BuiltinPredicate] booleanOr | +| file://:0:0:0:0 | booleanXor | semmle.label | [BuiltinPredicate] booleanXor | +| file://:0:0:0:0 | ceil | semmle.label | [BuiltinPredicate] ceil | +| file://:0:0:0:0 | charAt | semmle.label | [BuiltinPredicate] charAt | +| file://:0:0:0:0 | copySign | semmle.label | [BuiltinPredicate] copySign | +| file://:0:0:0:0 | cos | semmle.label | [BuiltinPredicate] cos | +| file://:0:0:0:0 | cosh | semmle.label | [BuiltinPredicate] cosh | +| file://:0:0:0:0 | daysTo | semmle.label | [BuiltinPredicate] daysTo | +| file://:0:0:0:0 | exp | semmle.label | [BuiltinPredicate] exp | +| file://:0:0:0:0 | floor | semmle.label | [BuiltinPredicate] floor | +| file://:0:0:0:0 | gcd | semmle.label | [BuiltinPredicate] gcd | +| file://:0:0:0:0 | getAQlClass | semmle.label | [BuiltinPredicate] getAQlClass | +| file://:0:0:0:0 | getDay | semmle.label | [BuiltinPredicate] getDay | +| file://:0:0:0:0 | getHours | semmle.label | [BuiltinPredicate] getHours | +| file://:0:0:0:0 | getMinutes | semmle.label | [BuiltinPredicate] getMinutes | +| file://:0:0:0:0 | getMonth | semmle.label | [BuiltinPredicate] getMonth | +| file://:0:0:0:0 | getSeconds | semmle.label | [BuiltinPredicate] getSeconds | +| file://:0:0:0:0 | getYear | semmle.label | [BuiltinPredicate] getYear | +| file://:0:0:0:0 | indexOf | semmle.label | [BuiltinPredicate] indexOf | +| file://:0:0:0:0 | indexOf | semmle.label | [BuiltinPredicate] indexOf | +| file://:0:0:0:0 | isLowercase | semmle.label | [BuiltinPredicate] isLowercase | +| file://:0:0:0:0 | isUppercase | semmle.label | [BuiltinPredicate] isUppercase | +| file://:0:0:0:0 | length | semmle.label | [BuiltinPredicate] length | +| file://:0:0:0:0 | log | semmle.label | [BuiltinPredicate] log | +| file://:0:0:0:0 | log | semmle.label | [BuiltinPredicate] log | +| file://:0:0:0:0 | log2 | semmle.label | [BuiltinPredicate] log2 | +| file://:0:0:0:0 | log10 | semmle.label | [BuiltinPredicate] log10 | +| file://:0:0:0:0 | matches | semmle.label | [BuiltinPredicate] matches | +| file://:0:0:0:0 | maximum | semmle.label | [BuiltinPredicate] maximum | +| file://:0:0:0:0 | minimum | semmle.label | [BuiltinPredicate] minimum | +| file://:0:0:0:0 | nextAfter | semmle.label | [BuiltinPredicate] nextAfter | +| file://:0:0:0:0 | nextDown | semmle.label | [BuiltinPredicate] nextDown | +| file://:0:0:0:0 | nextUp | semmle.label | [BuiltinPredicate] nextUp | +| 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 | +| file://:0:0:0:0 | signum | semmle.label | [BuiltinPredicate] signum | +| file://:0:0:0:0 | sin | semmle.label | [BuiltinPredicate] sin | +| file://:0:0:0:0 | sinh | semmle.label | [BuiltinPredicate] sinh | +| file://:0:0:0:0 | splitAt | semmle.label | [BuiltinPredicate] splitAt | +| file://:0:0:0:0 | splitAt | semmle.label | [BuiltinPredicate] splitAt | +| file://:0:0:0:0 | sqrt | semmle.label | [BuiltinPredicate] sqrt | +| file://:0:0:0:0 | substring | semmle.label | [BuiltinPredicate] substring | +| file://:0:0:0:0 | suffix | semmle.label | [BuiltinPredicate] suffix | +| file://:0:0:0:0 | tan | semmle.label | [BuiltinPredicate] tan | +| file://:0:0:0:0 | tanh | semmle.label | [BuiltinPredicate] tanh | +| file://:0:0:0:0 | toDate | semmle.label | [BuiltinPredicate] toDate | +| file://:0:0:0:0 | toFloat | semmle.label | [BuiltinPredicate] toFloat | +| file://:0:0:0:0 | toISO | semmle.label | [BuiltinPredicate] toISO | +| file://:0:0:0:0 | toInt | semmle.label | [BuiltinPredicate] toInt | +| file://:0:0:0:0 | toLowerCase | semmle.label | [BuiltinPredicate] toLowerCase | +| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString | +| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString | +| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString | +| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString | +| file://:0:0:0:0 | toString | semmle.label | [BuiltinPredicate] toString | +| file://:0:0:0:0 | toUnicode | semmle.label | [BuiltinPredicate] toUnicode | +| file://:0:0:0:0 | toUpperCase | semmle.label | [BuiltinPredicate] toUpperCase | +| file://:0:0:0:0 | toUrl | semmle.label | [BuiltinPredicate] toUrl | +| file://:0:0:0:0 | toUrl | semmle.label | [BuiltinPredicate] toUrl | +| file://:0:0:0:0 | trim | semmle.label | [BuiltinPredicate] trim | +| file://:0:0:0:0 | ulp | semmle.label | [BuiltinPredicate] ulp | +| printAst.ql:1:1:1:28 | Import | semmle.label | [Import] Import | +| printAst.ql:1:1:1:28 | Import | semmle.order | 77 | +| printAst.ql:1:1:1:29 | TopLevel | semmle.label | [TopLevel] TopLevel | +| printAst.ql:1:1:1:29 | TopLevel | semmle.order | 77 | +edges +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:1:1:1:17 | Import | semmle.label | getAnImport() | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:1:1:1:17 | Import | semmle.order | 1 | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:3:1:7:1 | Class Foo | semmle.label | getAClass() | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:3:1:7:1 | Class Foo | semmle.order | 3 | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.label | getAPredicate() | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:9:7:11:1 | ClasslessPredicate foo | semmle.order | 15 | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.label | getAPredicate() | +| Foo.qll:1:1:27:2 | TopLevel | Foo.qll:13:1:27:1 | ClasslessPredicate calls | semmle.order | 31 | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:3:19:3:22 | TypeExpr | semmle.label | getASuperType() | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:3:19:3:22 | TypeExpr | semmle.order | 4 | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:4:3:4:17 | CharPred Foo | semmle.label | getCharPred() | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:4:3:4:17 | CharPred Foo | semmle.order | 5 | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:6:3:6:38 | ClassPredicate toString | semmle.label | getClassPredicate(_) | +| Foo.qll:3:1:7:1 | Class Foo | Foo.qll:6:3:6:38 | ClassPredicate toString | semmle.order | 9 | +| Foo.qll:4:3:4:17 | CharPred Foo | Foo.qll:4:11:4:15 | ComparisonFormula | semmle.label | getBody() | +| Foo.qll:4:3:4:17 | CharPred Foo | Foo.qll:4:11:4:15 | ComparisonFormula | semmle.order | 6 | +| Foo.qll:4:11:4:15 | ComparisonFormula | Foo.qll:4:11:4:11 | Integer | semmle.label | getLeftOperand() | +| Foo.qll:4:11:4:15 | ComparisonFormula | Foo.qll:4:11:4:11 | Integer | semmle.order | 6 | +| Foo.qll:4:11:4:15 | ComparisonFormula | Foo.qll:4:15:4:15 | Integer | semmle.label | getRightOperand() | +| Foo.qll:4:11:4:15 | ComparisonFormula | Foo.qll:4:15:4:15 | Integer | semmle.order | 8 | +| Foo.qll:6:3:6:38 | ClassPredicate toString | Foo.qll:6:3:6:8 | TypeExpr | semmle.label | getReturnTypeExpr() | +| Foo.qll:6:3:6:38 | ClassPredicate toString | Foo.qll:6:3:6:8 | TypeExpr | semmle.order | 9 | +| Foo.qll:6:3:6:38 | ClassPredicate toString | Foo.qll:6:23:6:36 | ComparisonFormula | semmle.label | getBody() | +| Foo.qll:6:3:6:38 | ClassPredicate toString | Foo.qll:6:23:6:36 | ComparisonFormula | semmle.order | 11 | +| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:23:6:28 | result | semmle.label | getLeftOperand() | +| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:23:6:28 | result | semmle.order | 11 | +| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:32:6:36 | String | semmle.label | getRightOperand() | +| Foo.qll:6:23:6:36 | ComparisonFormula | Foo.qll:6:32:6:36 | String | semmle.order | 13 | +| Foo.qll:9:1:9:5 | annotation | Foo.qll:9:1:9:5 | annotation | semmle.label | getAnAnnotation() | +| Foo.qll:9:1:9:5 | annotation | Foo.qll:9:1:9:5 | annotation | semmle.order | 14 | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:1:9:5 | annotation | semmle.label | getAnAnnotation() | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:1:9:5 | annotation | semmle.order | 14 | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:21:9:25 | f | semmle.label | getParameter(_) | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:9:21:9:25 | f | semmle.order | 16 | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:10:3:10:85 | ComparisonFormula | semmle.label | getBody() | +| Foo.qll:9:7:11:1 | ClasslessPredicate foo | Foo.qll:10:3:10:85 | ComparisonFormula | semmle.order | 18 | +| Foo.qll:9:21:9:25 | f | Foo.qll:9:21:9:23 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:9:21:9:25 | f | Foo.qll:9:21:9:23 | TypeExpr | semmle.order | 16 | +| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:3:10:3 | f | semmle.label | getLeftOperand() | +| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:3:10:3 | f | semmle.order | 18 | +| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:7:10:85 | Rank | semmle.label | getRightOperand() | +| Foo.qll:10:3:10:85 | ComparisonFormula | Foo.qll:10:7:10:85 | Rank | semmle.order | 20 | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:12:10:12 | Integer | semmle.label | getRankExpr() | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:12:10:12 | Integer | semmle.order | 21 | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:15:10:23 | inner | semmle.label | getArgument(_) | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:15:10:23 | inner | semmle.order | 22 | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:27:10:50 | ComparisonFormula | semmle.label | getRange() | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:27:10:50 | ComparisonFormula | semmle.order | 24 | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:54:10:58 | inner | semmle.label | getExpr(_) | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:54:10:58 | inner | semmle.order | 28 | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:69:10:84 | MemberCall | semmle.label | getOrderBy(_) | +| Foo.qll:10:7:10:85 | Rank | Foo.qll:10:69:10:84 | MemberCall | semmle.order | 29 | +| Foo.qll:10:15:10:23 | inner | Foo.qll:10:15:10:17 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:10:15:10:23 | inner | Foo.qll:10:15:10:17 | TypeExpr | semmle.order | 22 | +| Foo.qll:10:27:10:42 | MemberCall | Foo.qll:10:27:10:31 | inner | semmle.label | getBase() | +| Foo.qll:10:27:10:42 | MemberCall | Foo.qll:10:27:10:31 | inner | semmle.order | 24 | +| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:27:10:42 | MemberCall | semmle.label | getLeftOperand() | +| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:27:10:42 | MemberCall | semmle.order | 24 | +| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:46:10:50 | String | semmle.label | getRightOperand() | +| Foo.qll:10:27:10:50 | ComparisonFormula | Foo.qll:10:46:10:50 | String | semmle.order | 27 | +| Foo.qll:10:69:10:84 | MemberCall | Foo.qll:10:69:10:73 | inner | semmle.label | getBase() | +| Foo.qll:10:69:10:84 | MemberCall | Foo.qll:10:69:10:73 | inner | semmle.order | 29 | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:13:17:13:21 | f | semmle.label | getParameter(_) | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:13:17:13:21 | f | semmle.order | 32 | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:14:3:26:14 | Disjunction | semmle.label | getBody() | +| Foo.qll:13:1:27:1 | ClasslessPredicate calls | Foo.qll:14:3:26:14 | Disjunction | semmle.order | 34 | +| Foo.qll:13:17:13:21 | f | Foo.qll:13:17:13:19 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:13:17:13:21 | f | Foo.qll:13:17:13:19 | TypeExpr | semmle.order | 32 | +| Foo.qll:14:3:14:10 | PredicateCall | Foo.qll:14:9:14:9 | f | semmle.label | getArgument(_) | +| Foo.qll:14:3:14:10 | PredicateCall | Foo.qll:14:9:14:9 | f | semmle.order | 41 | +| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:14:3:14:10 | PredicateCall | semmle.label | getAnOperand() | +| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:14:3:14:10 | PredicateCall | semmle.order | 34 | +| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:16:3:16:29 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:16:29 | Disjunction | Foo.qll:16:3:16:29 | ComparisonFormula | semmle.order | 42 | +| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:14:3:16:29 | Disjunction | semmle.label | getAnOperand() | +| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:14:3:16:29 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:18:3:18:28 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:18:28 | Disjunction | Foo.qll:18:3:18:28 | ComparisonFormula | semmle.order | 49 | +| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:14:3:18:28 | Disjunction | semmle.label | getAnOperand() | +| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:14:3:18:28 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:20:3:20:13 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:20:13 | Disjunction | Foo.qll:20:3:20:13 | ComparisonFormula | semmle.order | 55 | +| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:14:3:20:13 | Disjunction | semmle.label | getAnOperand() | +| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:14:3:20:13 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:22:3:22:16 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:22:16 | Disjunction | Foo.qll:22:3:22:16 | ComparisonFormula | semmle.order | 60 | +| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:14:3:22:16 | Disjunction | semmle.label | getAnOperand() | +| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:14:3:22:16 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:24:3:24:23 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:24:23 | Disjunction | Foo.qll:24:3:24:23 | ComparisonFormula | semmle.order | 65 | +| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:14:3:24:23 | Disjunction | semmle.label | getAnOperand() | +| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:14:3:24:23 | Disjunction | semmle.order | 34 | +| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:26:3:26:14 | ComparisonFormula | semmle.label | getAnOperand() | +| Foo.qll:14:3:26:14 | Disjunction | Foo.qll:26:3:26:14 | ComparisonFormula | semmle.order | 74 | +| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:3:16:7 | String | semmle.label | getLeftOperand() | +| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:3:16:7 | String | semmle.order | 42 | +| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:11:16:29 | MemberCall | semmle.label | getRightOperand() | +| Foo.qll:16:3:16:29 | ComparisonFormula | Foo.qll:16:11:16:29 | MemberCall | semmle.order | 44 | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:11:16:11 | f | semmle.label | getBase() | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:11:16:11 | f | semmle.order | 44 | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:22:16:22 | Integer | semmle.label | getArgument(_) | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:22:16:22 | Integer | semmle.order | 46 | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:25:16:25 | Integer | semmle.label | getArgument(_) | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:25:16:25 | Integer | semmle.order | 47 | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:28:16:28 | Integer | semmle.label | getArgument(_) | +| Foo.qll:16:11:16:29 | MemberCall | Foo.qll:16:28:16:28 | Integer | semmle.order | 48 | +| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:3:18:3 | f | semmle.label | getBase() | +| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:3:18:3 | f | semmle.order | 49 | +| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:6:18:8 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:18:3:18:9 | InlineCast | Foo.qll:18:6:18:8 | TypeExpr | semmle.order | 53 | +| Foo.qll:18:3:18:20 | MemberCall | Foo.qll:18:3:18:9 | InlineCast | semmle.label | getBase() | +| Foo.qll:18:3:18:20 | MemberCall | Foo.qll:18:3:18:9 | InlineCast | semmle.order | 49 | +| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:3:18:20 | MemberCall | semmle.label | getLeftOperand() | +| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:3:18:20 | MemberCall | semmle.order | 49 | +| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:24:18:28 | String | semmle.label | getRightOperand() | +| Foo.qll:18:3:18:28 | ComparisonFormula | Foo.qll:18:24:18:28 | String | semmle.order | 54 | +| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:3:20:3 | f | semmle.label | getBase() | +| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:3:20:3 | f | semmle.order | 55 | +| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:6:20:8 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:20:3:20:9 | InlineCast | Foo.qll:20:6:20:8 | TypeExpr | semmle.order | 58 | +| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:3:20:9 | InlineCast | semmle.label | getLeftOperand() | +| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:3:20:9 | InlineCast | semmle.order | 55 | +| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.label | getRightOperand() | +| Foo.qll:20:3:20:13 | ComparisonFormula | Foo.qll:20:13:20:13 | f | semmle.order | 59 | +| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.label | getLeftOperand() | +| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:3:22:3 | f | semmle.order | 60 | +| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.label | getRightOperand() | +| Foo.qll:22:3:22:16 | ComparisonFormula | Foo.qll:22:7:22:16 | FullAggregate[any] | semmle.order | 62 | +| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.label | getArgument(_) | +| Foo.qll:22:7:22:16 | FullAggregate[any] | Foo.qll:22:11:22:15 | f | semmle.order | 63 | +| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.label | getTypeExpr() | +| Foo.qll:22:11:22:15 | f | Foo.qll:22:11:22:13 | TypeExpr | semmle.order | 63 | +| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.label | getLeftOperand() | +| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:3:24:3 | Integer | semmle.order | 65 | +| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:7:24:23 | AddExpr | semmle.label | getRightOperand() | +| Foo.qll:24:3:24:23 | ComparisonFormula | Foo.qll:24:7:24:23 | AddExpr | semmle.order | 67 | +| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:7:24:7 | Integer | semmle.label | getLeftOperand() | +| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:7:24:7 | Integer | semmle.order | 67 | +| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:12:24:22 | AddExpr | semmle.label | getRightOperand() | +| Foo.qll:24:7:24:23 | AddExpr | Foo.qll:24:12:24:22 | AddExpr | semmle.order | 69 | +| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:12:24:12 | Integer | semmle.label | getLeftOperand() | +| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:12:24:12 | Integer | semmle.order | 69 | +| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:17:24:21 | AddExpr | semmle.label | getRightOperand() | +| Foo.qll:24:12:24:22 | AddExpr | Foo.qll:24:17:24:21 | AddExpr | semmle.order | 71 | +| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:17:24:17 | Integer | semmle.label | getLeftOperand() | +| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:17:24:17 | Integer | semmle.order | 71 | +| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:21:24:21 | Integer | semmle.label | getRightOperand() | +| Foo.qll:24:17:24:21 | AddExpr | Foo.qll:24:21:24:21 | Integer | semmle.order | 73 | +| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:3:26:6 | Boolean | semmle.label | getLeftOperand() | +| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:3:26:6 | Boolean | semmle.order | 74 | +| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:10:26:14 | Boolean | semmle.label | getRightOperand() | +| Foo.qll:26:3:26:14 | ComparisonFormula | Foo.qll:26:10:26:14 | Boolean | semmle.order | 76 | +| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.label | getAnImport() | +| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.order | 77 | +graphProperties +| semmle.graphKind | tree | diff --git a/ql/ql/test/printAst/printAst.ql b/ql/ql/test/printAst/printAst.ql new file mode 100644 index 000000000000..ba70af634ed7 --- /dev/null +++ b/ql/ql/test/printAst/printAst.ql @@ -0,0 +1 @@ +import codeql_ql.printAstAst diff --git a/ql/ql/test/qlpack.yml b/ql/ql/test/qlpack.yml new file mode 100644 index 000000000000..10b6271535ed --- /dev/null +++ b/ql/ql/test/qlpack.yml @@ -0,0 +1,7 @@ +name: codeql-ql-tests +version: 0.0.0 +libraryPathDependencies: + - codeql-ql + - codeql-ql-examples +extractor: ql +tests: . diff --git a/ql/ql/test/queries/performance/VarUnusedInDisjunct/Test.qll b/ql/ql/test/queries/performance/VarUnusedInDisjunct/Test.qll new file mode 100644 index 000000000000..9fde97a35443 --- /dev/null +++ b/ql/ql/test/queries/performance/VarUnusedInDisjunct/Test.qll @@ -0,0 +1,167 @@ +import ql + +class Big = Expr; + +class Small extends boolean { + Small() { this = [true, false] } +} + +class MyStr extends string { + MyStr() { this = ["foo", "bar"] } +} + +predicate bad1(Big b) { + b.toString().matches("%foo") + or + any() +} + +int bad2() { + exists(Big big, Small small | + result = big.toString().toInt() + or + result = small.toString().toInt() + ) +} + +float bad3(Big t) { + result = [1 .. 10].toString().toFloat() or + result = [11 .. 20].toString().toFloat() or + result = t.toString().toFloat() or + result = [21 .. 30].toString().toFloat() +} + +string good1(Big t) { + ( + result = t.toString() + or + result instanceof MyStr // <- t unused here, but that's ok because of the conjunct that binds t. + ) and + t.toString().regexpMatch(".*foo") +} + +predicate helper(Big a, Big b) { + a = b and + a.toString().matches("%foo") +} + +predicate bad4(Big fromType, Big toType) { + helper(fromType, toType) + or + fromType.toString().matches("%foo") + or + helper(toType, fromType) +} + +predicate good2(Big t) { + exists(Small other | + t.toString().matches("%foo") + or + other.toString().matches("%foo") // <- t unused here, but that's ok because of the conjunct (exists) that binds t. + | + t.toString().regexpMatch(".*foo") + ) +} + +predicate mixed1(Big good, Small small) { + good.toString().matches("%foo") + or + good = + any(Big bad | + small.toString().matches("%foo") and + // the use of good is fine, the comparison futher up binds it. + // the same is not true for bad. + (bad.toString().matches("%foo") or good.toString().regexpMatch("foo.*")) and + small.toString().regexpMatch(".*foo") + ) +} + +newtype OtherSmall = + Small1() or + Small2(boolean b) { b = true } or + Small3(boolean b, Small o) { + b = true and + o.toString().matches("%foo") + } + +predicate good3(OtherSmall small) { + small = Small1() + or + 1 = 1 +} + +predicate good4(Big big, Small small) { + big.toString().matches("%foo") + or + // assignment to small type, intentional cartesian product + small = any(Small s | s.toString().matches("%foo")) +} + +predicate good5(Big bb, Big v, boolean certain) { + exists(Big read | + read = bb and + read = v and + certain = true + ) + or + v = + any(Big lsv | + lsv.getEnclosingPredicate() = bb.(Expr).getEnclosingPredicate() and + (lsv.toString().matches("%foo") or v.toString().matches("%foo")) and + certain = false + ) +} + +predicate bad5(Big bb) { if none() then bb.toString().matches("%foo") else any() } + +pragma[inline] +predicate good5(Big a, Big b) { + // fine. Assumes it's used somewhere where `a` and `b` are bound. + b = any(Big bb | bb.toString().matches("%foo")) + or + a = any(Big bb | bb.toString().matches("%foo")) +} + +predicate bad6(Big a) { + ( + a.toString().matches("%foo") // bad + or + any() + ) and + ( + a.toString().matches("%foo") // also bad + or + any() + ) +} + +predicate good6(Big a) { + a.toString().matches("%foo") and + ( + a.toString().matches("%foo") // good, `a` is bound on the branch of the conjunction. + or + any() + ) +} + +predicate good7() { + exists(Big l, Big r | + l.toString().matches("%foo1") and + r.toString().matches("%foo2") + or + l.toString().matches("%foo3") and + r.toString().matches("%foo4") + | + not (l.toString().regexpMatch("%foo5") or r.toString().regexpMatch("%foo6")) and + (l.toString().regexpMatch("%foo7") or r.toString().regexpMatch("%foo8")) + ) +} + +// TOOD: Next test, this one is +string good8(int bitSize) { + if bitSize != 0 + then bitSize = 1 and result = bitSize.toString() + else ( + if 1 = 0 then result = "foo" else result = "bar" + ) +} diff --git a/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.expected b/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.expected new file mode 100644 index 000000000000..088c3a3b5050 --- /dev/null +++ b/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.expected @@ -0,0 +1,8 @@ +| Test.qll:14:3:16:7 | Disjunction | The variable b is only used in one side of disjunct. | +| Test.qll:21:5:23:37 | Disjunction | The variable big is only used in one side of disjunct. | +| Test.qll:28:3:30:33 | Disjunction | The variable t is only used in one side of disjunct. | +| Test.qll:49:3:53:26 | Disjunction | The variable toType is only used in one side of disjunct. | +| Test.qll:74:8:74:77 | Disjunction | The variable bad is only used in one side of disjunct. | +| Test.qll:115:26:115:80 | IfFormula | The variable bb is only used in one side of disjunct. | +| Test.qll:127:5:129:9 | Disjunction | The variable a is only used in one side of disjunct. | +| Test.qll:132:5:134:9 | Disjunction | The variable a is only used in one side of disjunct. | diff --git a/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.qlref b/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.qlref new file mode 100644 index 000000000000..28f0c0d938a1 --- /dev/null +++ b/ql/ql/test/queries/performance/VarUnusedInDisjunct/VarUnusedInDisjunct.qlref @@ -0,0 +1 @@ +queries/performance/VarUnusedInDisjunct.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/ImplicitThis/Bad.qll b/ql/ql/test/queries/style/ImplicitThis/Bad.qll new file mode 100644 index 000000000000..97b51284acc5 --- /dev/null +++ b/ql/ql/test/queries/style/ImplicitThis/Bad.qll @@ -0,0 +1,11 @@ +import ql + +class Foo extends string { + Foo() { this = "hello" } + + string getBar() { result = "bar" } + + string getBarWithThis() { result = this.getBar() } + + string getBarWithoutThis() { result = getBar() } +} diff --git a/ql/ql/test/queries/style/ImplicitThis/Good.qll b/ql/ql/test/queries/style/ImplicitThis/Good.qll new file mode 100644 index 000000000000..35b8023b572f --- /dev/null +++ b/ql/ql/test/queries/style/ImplicitThis/Good.qll @@ -0,0 +1,21 @@ +import ql + +class Foo extends string { + Foo() { this = "hello" } + + string getBar() { result = "bar" } + + string getBarWithThis() { result = this.getBar() } + + /* Okay because not a member predicate. */ + string getBaz() { result = Baz::baz() } + + /* Okay because not a member predicate. */ + string getOuterQuux() { result = getQuux() } +} + +string getQuux() { result = "quux" } + +module Baz { + string baz() { result = "baz" } +} diff --git a/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.expected b/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.expected new file mode 100644 index 000000000000..fa3adbaf992d --- /dev/null +++ b/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.expected @@ -0,0 +1 @@ +| Bad.qll:10:41:10:48 | PredicateCall | Use of implicit `this`. | diff --git a/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.qlref b/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.qlref new file mode 100644 index 000000000000..0bdcd3b4b5b0 --- /dev/null +++ b/ql/ql/test/queries/style/ImplicitThis/ImplicitThis.qlref @@ -0,0 +1 @@ +queries/style/ImplicitThis.ql diff --git a/ql/ql/test/queries/style/ImplicitThis/Okay.qll b/ql/ql/test/queries/style/ImplicitThis/Okay.qll new file mode 100644 index 000000000000..37c9dd4ab2ae --- /dev/null +++ b/ql/ql/test/queries/style/ImplicitThis/Okay.qll @@ -0,0 +1,13 @@ +import ql + +class Foo extends string { + Foo() { this = "hello" } + + string getBar() { result = "bar" } + + /* Okay, because we don't write `this.some_method` anywhere */ + string getBarWithoutThis() { result = getBar() } + + /* Okay, because this is the only way to cast `this`. */ + string useThisWithInlineCast() { result = this.(string).toUpperCase() } +} diff --git a/ql/ql/test/queries/style/MissingOverride/MissingOverride.expected b/ql/ql/test/queries/style/MissingOverride/MissingOverride.expected new file mode 100644 index 000000000000..f59b21d0d73c --- /dev/null +++ b/ql/ql/test/queries/style/MissingOverride/MissingOverride.expected @@ -0,0 +1 @@ +| Test.qll:12:3:12:33 | ClassPredicate test | Wrong.test overrides $@ but does not have an override annotation. | Test.qll:4:3:4:40 | ClassPredicate test | Super.test | diff --git a/ql/ql/test/queries/style/MissingOverride/MissingOverride.qlref b/ql/ql/test/queries/style/MissingOverride/MissingOverride.qlref new file mode 100644 index 000000000000..3a83310f4a29 --- /dev/null +++ b/ql/ql/test/queries/style/MissingOverride/MissingOverride.qlref @@ -0,0 +1 @@ +queries/style/MissingOverride.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/MissingOverride/Test.qll b/ql/ql/test/queries/style/MissingOverride/Test.qll new file mode 100644 index 000000000000..e7f6a2c6b877 --- /dev/null +++ b/ql/ql/test/queries/style/MissingOverride/Test.qll @@ -0,0 +1,13 @@ +import ql + +class Super extends AstNode { + predicate test(int i) { i = [1 .. 5] } +} + +class Correct extends Super { + override predicate test(int i) { i = 3 } +} + +class Wrong extends Super { + predicate test(int i) { i = 2 } +} diff --git a/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.expected b/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.expected new file mode 100644 index 000000000000..367ac8b8471e --- /dev/null +++ b/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.expected @@ -0,0 +1,2 @@ +| Test.qll:12:27:12:35 | succ | succ was $@ in the super class. | Test.qll:4:27:4:35 | pred | named pred | +| Test.qll:12:38:12:46 | pred | pred was $@ in the super class. | Test.qll:4:38:4:46 | succ | named succ | diff --git a/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.qlref b/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.qlref new file mode 100644 index 000000000000..cab8c347410b --- /dev/null +++ b/ql/ql/test/queries/style/SwappedParameterNames/SwappedParameterNames.qlref @@ -0,0 +1 @@ +queries/style/SwappedParameterNames.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/SwappedParameterNames/Test.qll b/ql/ql/test/queries/style/SwappedParameterNames/Test.qll new file mode 100644 index 000000000000..5c8083d3098f --- /dev/null +++ b/ql/ql/test/queries/style/SwappedParameterNames/Test.qll @@ -0,0 +1,13 @@ +import ql + +class Sup extends AstNode { + abstract predicate step(Expr pred, Expr succ); +} + +class Correct extends Sup { + override predicate step(Expr pred, Expr succ) { none() } +} + +class Wrong extends Sup { + override predicate step(Expr succ, Expr pred) { none() } // <- swapped parameter names +} diff --git a/ql/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected b/ql/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected new file mode 100644 index 000000000000..fac79ff078ed --- /dev/null +++ b/ql/ql/test/queries/style/UseSetLiteral/UseSetLiteral.expected @@ -0,0 +1,8 @@ +| 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/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref b/ql/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref new file mode 100644 index 000000000000..d4047ebc29fd --- /dev/null +++ b/ql/ql/test/queries/style/UseSetLiteral/UseSetLiteral.qlref @@ -0,0 +1 @@ +queries/style/UseSetLiteral.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/UseSetLiteral/test.qll b/ql/ql/test/queries/style/UseSetLiteral/test.qll new file mode 100644 index 000000000000..36a5f938f895 --- /dev/null +++ b/ql/ql/test/queries/style/UseSetLiteral/test.qll @@ -0,0 +1,135 @@ +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 // GOOD + or + a = 2 and b = 4 + or + a = 3 and b = 9 + or + a = 4 and b = 16 +} + +predicate test14(int a) { + a = 1 // BAD + or + ( + (a = 2 or a = 3) + or + a = 4 + ) +} diff --git a/ql/ql/test/type/Test.qll b/ql/ql/test/type/Test.qll new file mode 100644 index 000000000000..2ff331cef04c --- /dev/null +++ b/ql/ql/test/type/Test.qll @@ -0,0 +1,30 @@ +import ql + +class Strings extends string { + Strings() { this = ["", "f", "o", "foo", "bar", "b", "a", "r", "ba", "ar"] } +} + +class Floats extends float { + Floats() { this = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] } +} + +string conc(Strings a, Strings b) { result = a + b } + +float floats(Floats a, Floats b) { result = a + b } + +class Base extends string { + Base() { this = "foo" } + + int foo() { result = 1 } +} + +class Sub extends Base { + Sub() { this = "bar" } + + int bar() { result = Base.super.foo() } + + int bar2() { result = super.foo() } +} + +bindingset[result, a, b] +int integerMul(int a, int b) { result = a * b } diff --git a/ql/ql/test/type/type.expected b/ql/ql/test/type/type.expected new file mode 100644 index 000000000000..773a9244a224 --- /dev/null +++ b/ql/ql/test/type/type.expected @@ -0,0 +1,56 @@ +| Test.qll:4:15:4:18 | this | Test.qll:3:1:5:1 | Strings | +| Test.qll:4:15:4:18 | this | Test.qll:3:1:5:1 | Strings.Strings | +| Test.qll:4:15:4:18 | this | Test.qll:3:1:5:1 | Strings.extends | +| Test.qll:4:22:4:76 | Set | file://:0:0:0:0 | string | +| Test.qll:4:23:4:24 | String | file://:0:0:0:0 | string | +| Test.qll:4:27:4:29 | String | file://:0:0:0:0 | string | +| Test.qll:4:32:4:34 | String | file://:0:0:0:0 | string | +| Test.qll:4:37:4:41 | String | file://:0:0:0:0 | string | +| Test.qll:4:44:4:48 | String | file://:0:0:0:0 | string | +| Test.qll:4:51:4:53 | String | file://:0:0:0:0 | string | +| Test.qll:4:56:4:58 | String | file://:0:0:0:0 | string | +| Test.qll:4:61:4:63 | String | file://:0:0:0:0 | string | +| Test.qll:4:66:4:69 | String | file://:0:0:0:0 | string | +| Test.qll:4:72:4:75 | String | file://:0:0:0:0 | string | +| Test.qll:8:14:8:17 | this | Test.qll:7:1:9:1 | Floats | +| Test.qll:8:14:8:17 | this | Test.qll:7:1:9:1 | Floats.Floats | +| Test.qll:8:14:8:17 | this | Test.qll:7:1:9:1 | Floats.extends | +| Test.qll:8:21:8:70 | Set | file://:0:0:0:0 | float | +| Test.qll:8:22:8:24 | Float | file://:0:0:0:0 | float | +| Test.qll:8:27:8:29 | Float | file://:0:0:0:0 | float | +| Test.qll:8:32:8:34 | Float | file://:0:0:0:0 | float | +| Test.qll:8:37:8:39 | Float | file://:0:0:0:0 | float | +| Test.qll:8:42:8:44 | Float | file://:0:0:0:0 | float | +| Test.qll:8:47:8:49 | Float | file://:0:0:0:0 | float | +| Test.qll:8:52:8:54 | Float | file://:0:0:0:0 | float | +| Test.qll:8:57:8:59 | Float | file://:0:0:0:0 | float | +| Test.qll:8:62:8:64 | Float | file://:0:0:0:0 | float | +| Test.qll:8:67:8:69 | Float | file://:0:0:0:0 | float | +| Test.qll:11:37:11:42 | result | file://:0:0:0:0 | string | +| Test.qll:11:46:11:46 | a | Test.qll:3:1:5:1 | Strings | +| Test.qll:11:46:11:50 | AddExpr | file://:0:0:0:0 | string | +| Test.qll:11:50:11:50 | b | Test.qll:3:1:5:1 | Strings | +| Test.qll:13:36:13:41 | result | file://:0:0:0:0 | float | +| Test.qll:13:45:13:45 | a | Test.qll:7:1:9:1 | Floats | +| Test.qll:13:45:13:49 | AddExpr | file://:0:0:0:0 | float | +| Test.qll:13:49:13:49 | b | Test.qll:7:1:9:1 | Floats | +| Test.qll:16:12:16:15 | this | Test.qll:15:1:19:1 | Base | +| Test.qll:16:12:16:15 | this | Test.qll:15:1:19:1 | Base.Base | +| Test.qll:16:12:16:15 | this | Test.qll:15:1:19:1 | Base.extends | +| Test.qll:16:19:16:23 | String | file://:0:0:0:0 | string | +| Test.qll:18:15:18:20 | result | file://:0:0:0:0 | int | +| Test.qll:18:24:18:24 | Integer | file://:0:0:0:0 | int | +| Test.qll:22:11:22:14 | this | Test.qll:21:1:27:1 | Sub | +| Test.qll:22:11:22:14 | this | Test.qll:21:1:27:1 | Sub.Sub | +| Test.qll:22:11:22:14 | this | Test.qll:21:1:27:1 | Sub.extends | +| Test.qll:22:18:22:22 | String | file://:0:0:0:0 | string | +| Test.qll:24:15:24:20 | result | file://:0:0:0:0 | int | +| Test.qll:24:24:24:33 | Super | Test.qll:15:1:19:1 | Base | +| Test.qll:24:24:24:39 | MemberCall | file://:0:0:0:0 | int | +| Test.qll:26:16:26:21 | result | file://:0:0:0:0 | int | +| Test.qll:26:25:26:29 | Super | Test.qll:15:1:19:1 | Base | +| Test.qll:26:25:26:35 | MemberCall | file://:0:0:0:0 | int | +| Test.qll:30:32:30:37 | result | file://:0:0:0:0 | int | +| Test.qll:30:41:30:41 | a | file://:0:0:0:0 | int | +| Test.qll:30:41:30:45 | MulExpr | file://:0:0:0:0 | int | +| Test.qll:30:45:30:45 | b | file://:0:0:0:0 | int | diff --git a/ql/ql/test/type/type.ql b/ql/ql/test/type/type.ql new file mode 100644 index 000000000000..d2dad5fa3eff --- /dev/null +++ b/ql/ql/test/type/type.ql @@ -0,0 +1,3 @@ +import ql + +query Type getType(Expr e) { result = e.getType() } diff --git a/ql/scripts/identical-files.json b/ql/scripts/identical-files.json new file mode 100644 index 000000000000..ee52deb2fb67 --- /dev/null +++ b/ql/scripts/identical-files.json @@ -0,0 +1,18 @@ +{ + "SSA": [ + "codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", + "ql/src/codeql_ql/dataflow/internal/SsaImplCommon.qll" + ], + "DataFlow Common": [ + "codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll", + "ql/src/codeql_ql/dataflow/internal/DataFlowImplCommon.qll" + ], + "DataFlow": [ + "codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll", + "ql/src/codeql_ql/dataflow/internal/DataFlowImpl.qll" + ], + "TypeTracker": [ + "codeql/python/ql/src/experimental/typetracking/TypeTracker.qll", + "ql/src/codeql_ql/typetracking/TypeTracker.qll" + ] +} \ No newline at end of file diff --git a/ql/scripts/merge_stats.py b/ql/scripts/merge_stats.py new file mode 100644 index 000000000000..d2cbe0a3e644 --- /dev/null +++ b/ql/scripts/merge_stats.py @@ -0,0 +1,90 @@ +#!/usr/bin/python + +# This script merges a number of stats files to produce a single stats file. + +import sys +from lxml import etree +import argparse + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--output', required=True, help="Path of the output file.") + parser.add_argument('--normalise', required=True, help="Name of the relation to normalise the sizes on.") + parser.add_argument('--unscaled-stats', default=[], action='append', help="A stats file which should not be normalised.") + parser.add_argument('inputs', nargs='*', help="The other stats files") + return parser.parse_args() + +def die(msg): + sys.stderr.write('Error: ' + msg + '\n') + sys.exit(1) + +def main(): + args = parse_args() + inputs = args.inputs + output = args.output + normalise = args.normalise + unscaled_stats = args.unscaled_stats + + print("Merging %s into %s normalising on '%s'." % (', '.join(inputs), output, normalise)) + do_xml_files(output, inputs, unscaled_stats, normalise) + +def read_sized_xml(xml_file, name): + # Take the size of the named table as the size of the codebase + xml = etree.parse(xml_file) + ns = xml.xpath("stats/relation[name='%s']/cardinality" % name) + if len(ns) == 0: + die('Sized stats file ' + xml_file + ' does not have a cardinality for normalisation relation ' + name + '.') + n = ns[0] + size = int(n.text) + return (xml, size) + +def scale(xml, size, max_size): + # Scale up the contents of all the and tags + for v in xml.xpath(".//v|.//cardinality"): + v.text = str((int(v.text) * max_size) // size) + +def do_xml_files(output, scaled_xml_files, unscaled_xml_files, name): + # The result starts off empty + result = etree.Element("dbstats") + + # Scale all of the stats so that they might have come code bases of + # the same size + sized_xmls = [read_sized_xml(xml_file, name) + for xml_file in scaled_xml_files] + if sized_xmls != []: + max_size = max([size for (xml, size) in sized_xmls]) + for (xml, size) in sized_xmls: + scale(xml, size, max_size) + unsized_xmls = list(map(etree.parse, unscaled_xml_files)) + xmls = [xml for (xml, size) in sized_xmls] + unsized_xmls + + # Put all the stats in a single XML doc so that we can search them + # more easily + merged_xml = etree.Element("merged") + for xml in xmls: + merged_xml.append(xml.getroot()) + + # For each value of , take the tag with the biggest + typesizes = etree.SubElement(result, "typesizes") + typenames = sorted(set ([ typesize.find("k").text for typesize in merged_xml.xpath("dbstats/typesizes/e")])) + for typename in typenames: + xs = merged_xml.xpath("dbstats/typesizes/e[k='" + typename + "']") + sized_xs = [(int(x.find("v").text), x) for x in xs] + (_, x) = max(sized_xs, key = lambda p: p[0]) + typesizes.append(x) + + # For each value of , take the tag with + # the biggest + stats = etree.SubElement(result, "stats") + + relnames = sorted(set ([relation.find("name").text for relation in merged_xml.xpath("dbstats/stats/relation") ])) + for relname in relnames: + rels = merged_xml.xpath("dbstats/stats/relation[name='" + relname + "']") + sized_rels = [(int(rel.find("cardinality").text), rel) for rel in rels] + (_, rel) = max(sized_rels, key = lambda p: p[0]) + stats.append(rel) + + with open(output, 'wb') as f: + f.write(etree.tostring(result, pretty_print=True)) + +main() diff --git a/ql/scripts/sync-identical-files.py b/ql/scripts/sync-identical-files.py new file mode 100755 index 000000000000..17fe22808e9e --- /dev/null +++ b/ql/scripts/sync-identical-files.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +# Due to various technical limitations, we sometimes have files that need to be +# kept identical in the repository. This script loads a database of such +# files and can perform two functions: check whether they are still identical, +# and overwrite the others with a master copy if needed. +# The script that does the actual work is `sync-files.py`, which lives in the `codeql` submodule. +import sys +import os + +sys.path.append(os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../codeql/config'))) + +import importlib +syncfiles = importlib.import_module('sync-files') + +def chdir_repo_root(): + root_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..') + os.chdir(root_path) + +def sync_identical_files(): + if len(sys.argv) == 1: + master_file_picker = lambda files: None + elif len(sys.argv) == 2: + if sys.argv[1] == "--latest": + master_file_picker = syncfiles.choose_latest_file + elif os.path.isfile(sys.argv[1]): + master_file_picker = lambda files: syncfiles.choose_master_file(sys.argv[1], files) + else: + raise Exception("File not found") + else: + raise Exception("Bad command line or file not found") + chdir_repo_root() + syncfiles.load_if_exists('.', 'scripts/identical-files.json') + for group_name, files in syncfiles.file_groups.items(): + syncfiles.check_group(group_name, files, master_file_picker, syncfiles.emit_local_error) + +def main(): + sync_identical_files() + + if syncfiles.local_error_count > 0: + exit(1) + else: + print(__file__ +": All checks OK.") + +if __name__ == "__main__": + main() diff --git a/ql/tools/autobuild.cmd b/ql/tools/autobuild.cmd new file mode 100644 index 000000000000..b000600fea3c --- /dev/null +++ b/ql/tools/autobuild.cmd @@ -0,0 +1,12 @@ +@echo off + +type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^ + --include-extension=.ql ^ + --include-extension=.qll ^ + --include-extension=.dbscheme ^ + --include=**/qlpack.yml ^ + --size-limit=5m ^ + --language=ql ^ + "%CODEQL_EXTRACTOR_QL_WIP_DATABASE%" + +exit /b %ERRORLEVEL% diff --git a/ql/tools/autobuild.sh b/ql/tools/autobuild.sh new file mode 100755 index 000000000000..ef682d0980bc --- /dev/null +++ b/ql/tools/autobuild.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -eu + +exec "${CODEQL_DIST}/codeql" database index-files \ + --include-extension=.ql \ + --include-extension=.qll \ + --include-extension=.dbscheme \ + --include=**/qlpack.yml \ + --size-limit=5m \ + --language=ql \ + --working-dir=.\ + "$CODEQL_EXTRACTOR_QL_WIP_DATABASE" diff --git a/ql/tools/index-files.cmd b/ql/tools/index-files.cmd new file mode 100644 index 000000000000..f34b06f2aed9 --- /dev/null +++ b/ql/tools/index-files.cmd @@ -0,0 +1,8 @@ +@echo off + +type NUL && "%CODEQL_EXTRACTOR_QL_ROOT%\tools\win64\extractor.exe" ^ + --file-list "%1" ^ + --source-archive-dir "%CODEQL_EXTRACTOR_QL_SOURCE_ARCHIVE_DIR%" ^ + --output-dir "%CODEQL_EXTRACTOR_QL_TRAP_DIR%" + +exit /b %ERRORLEVEL% diff --git a/ql/tools/index-files.sh b/ql/tools/index-files.sh new file mode 100755 index 000000000000..c4bff2267475 --- /dev/null +++ b/ql/tools/index-files.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -eu + +exec "${CODEQL_EXTRACTOR_QL_ROOT}/tools/${CODEQL_PLATFORM}/extractor" \ + --file-list "$1" \ + --source-archive-dir "$CODEQL_EXTRACTOR_QL_SOURCE_ARCHIVE_DIR" \ + --output-dir "$CODEQL_EXTRACTOR_QL_TRAP_DIR" diff --git a/ql/tools/qltest.cmd b/ql/tools/qltest.cmd new file mode 100644 index 000000000000..2573d4f929e9 --- /dev/null +++ b/ql/tools/qltest.cmd @@ -0,0 +1,13 @@ +@echo off + +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%" + +exit /b %ERRORLEVEL% diff --git a/ql/tools/qltest.sh b/ql/tools/qltest.sh new file mode 100755 index 000000000000..3712b2a5a109 --- /dev/null +++ b/ql/tools/qltest.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -eu + +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=.\ + "$CODEQL_EXTRACTOR_QL_WIP_DATABASE"